“This is the first day of my participation in the August More Text Challenge.

General situation of

JUnit is an excellent open source Java unit testing framework. It is also the most used and most popular testing framework. JUnit is supported by Eclipse and IDEA.

Related words:

Unit testing: The examination and verification of the smallest testable units in software. The unit is the minimum function module under test. Unit testing is the lowest level of testing activity to be performed during software development, where individual units of software are tested in isolation from the rest of the program.

White box testing: White box testing is also called structural testing, transparent box testing, logic-driven testing, or code-based testing. White box testing is a test case design method where the box refers to the software being tested and the white box refers to the box being visible, i.e. it is clear what is inside the box and how it works. The “white box” method has a comprehensive understanding of the internal logical structure of the program and tests all logical paths. The “white box” method is an exhaustive path test. In using this scheme, the tester must examine the internal structure of the program, starting with the logic of the program, and derive test data. The number of independent paths through the program is astronomical.

Regression testing: Regression testing is when you modify old code and re-test it to make sure that the change did not introduce new errors or cause errors in other code. Automated regression testing will significantly reduce the cost of system testing, maintenance and upgrades.

For details, see JUnit 5 Official Documentation

The basic use

Spring Boot itself is integrated with JUnit, as shown in Maven:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
Copy the code

Specific test code can be written in SRC /test/ Java/project name/under the new Java file

package com.example.demo;
​
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
​
// flags the Spring Boot test class
@SpringBootTest
public class DemoTests {
​
​
    // It must be static before the entire test
    @BeforeAll
    static void initAll(a){
        System.out.println("BeforeAll init succeed!");
    }
​
    // Execute before each test method
    @BeforeEach
    void initEach(a){
        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
        System.out.println("BeforeEach init succeed!");
    }
​
    // Identifies as a test method
    @Test
    // Test method display name
    @DisplayName("java method test demo1")
    void calculateTest1(a){
        int actual = 1 + 2;
        // assert, check whether the value is equal, if so, continue execution
        Assertions.assertEquals(3, actual);
        System.out.println(actual);
    }
​
    @Test
    @DisplayName("java method test demo2")
    void calculateTest2(a){
        int actual = 1 + 2;
        / / assert that determine whether equal, otherwise throw an error org. Opentest4j. An AssertionFailedError is generated
        Assertions.assertEquals(5, actual);
        System.out.println(actual);
    }
​
    // execute after each test method
    @AfterEach
    void afterEachDestroy(a){
        System.out.println("AfterEach destroy succeed!");
        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
    }
​
    // Execute after the entire test. It must be static
    @AfterAll
    static void afterAllDestroy(a){
        System.out.println("AfterAll destroy succeed!"); }}Copy the code

The running results are as follows:

Demo1:

Demo2:

assertions

When we write code, we always make assumptions, and assertions are used to capture those assumptions in our code. The programmer believes that the expression value is true at a particular point in the program and that assertion validation can be enabled and disabled at any time, so assertions can be enabled at test time and disabled at deployment time. Similarly, after the program is up and running, end users can re-enable assertions if they encounter problems.

Using assertions creates more stable, better quality, and less error-prone code. Assertions are used when you need to interrupt the current operation when a value is FALSE. Unit tests must use assertions (Junit/JunitX).

The specific usage methods are as follows. In addition to assertEquals(), assertAll(), assertTimeout() and other methods, see the official documents for details.

    // Identifies as a test method
    @Test
    // Test method display name
    @DisplayName("java method test demo1")
    void calculateTest1(a){
        int actual = 1 + 2;
        // assert, check whether the value is equal, if so, continue execution
        Assertions.assertEquals(3, actual);
        System.out.println(actual);
    }
​
    @Test
    @DisplayName("java method test demo2")
    void calculateTest2(a){
        int actual = 1 + 2;
        / / assert that determine whether equal, otherwise throw an error org. Opentest4j. An AssertionFailedError is generated
        Assertions.assertEquals(5, actual);
        / / does not perform
        System.out.println(actual);
    }
Copy the code

Assuming that

In the org. Junit. Jupiter. API. Assumptions, encapsulates a set of methods, to support the test execution based on the assumption of conditions.

The hypothesis is essentially specifying a particular condition, and if the hypothesis cannot be met, the hypothesis does not cause the test to fail, but simply terminates the current test. This is also the biggest difference between a hypothesis and an assertion, because in the case of an assertion, the test will fail.

Personal understanding assertions are generally used to test whether the result is correct, while assumptions emphasize whether the preconditions of the test are correct. If not, the test is not necessary to continue, i.e. skip the test.

package com.example.demo;
​
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
​
import static org.junit.jupiter.api.Assumptions.assumeTrue;
​
@SpringBootTest
public class AssumeTest {
​
    @Test
    @DisplayName("assumeTestDemo1")
    void assumeTestDemo1(a){
        assumeTrue(true);
        System.out.println("assume true!");
    }
​
    @Test
    @DisplayName("assumeTestDemo2")
    void assumeTestDemo2(a){
        System.out.println("assume false!");
        assumeTrue(false);
        System.out.println("assume false!"); }}Copy the code

The running results are as follows:

Not:

Demo2:

Test one more piece of code to deepen your understanding.

package com.example.demo;
​
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
​
import static org.junit.jupiter.api.Assumptions.assumeTrue;
​
@SpringBootTest
public class AssumeTest {
​
    @BeforeAll
    static void beforeAssumeTest(a){
        assumeTrue(false);
    }
​
​
    @Test
    @DisplayName("assumeTestDemo1")
    void assumeTestDemo1(a){
        assumeTrue(true);
        System.out.println("assume true!");
    }
​
    @Test
    @DisplayName("assumeTestDemo2")
    void assumeTestDemo2(a){
        assumeTrue(false);
        System.out.println("assume false!"); }}Copy the code

The result is as follows:

Same with @beforeeach.

disable

Disabled is implemented with the @disabled annotation, which can be used to test classes and test methods. Marked classes or methods will not be executed.

package com.example.demo;
​
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
​
@SpringBootTest
public class DisabledTest {
​
    @Disabled
    @Test
    void disabledTestDemo1(a){
        System.out.println("disable1");
    }
​
    @Test
    void disabledTestDemo2(a){
        System.out.println("disable2"); }}Copy the code

The result is as follows:

The label

JUnit can test by Tag with the @tag annotation.

Example code is as follows:

TagTest1.java

@SpringBootTest
public class TagTest1 {
    
    @Test
    @Tag("hello")
    void TagTest1(a){
        System.out.println("hello1");
    }
    
    @Test
    @Tag("hi")
    void TagTest2(a){
        System.out.println("hi1"); }}Copy the code

TagTest2.java

@SpringBootTest
public class TagTest2 {
​
    @Test
    @Tag("hello")
    void TagTest3(a){
        System.out.println("hello2");
    }
​
    @Test
    @Tag("hi")
    void TagTest4(a){
        System.out.println("hi2"); }}Copy the code

Filter label hello. The result is as follows:

See that only test methods labeled “Hello” are executed.

Test the instance life cycle

JUnit 5’s default test instance life cycle is per-method, which means that for each test method, a test class is re-instantiated. This is why the @beforeAll and @Afterall embellished methods need to be static.

However, the Lifecycle of a TestInstance can be modified to execute all test methods on the same TestInstance via @testinstance (Lifecycle.PER_CLASS). When using this pattern, a new test instance is created for each test class. Therefore, if your test method depends on state stored in instance variables, you may need to reset that state in the @beforeeach or @Aftereach methods.

Nested test

When a test class has a large number of functions to test, the structure of the test class becomes complex. At this point, we can optimize the structure of the test class with annotations @nested and inner class forms to make the test presentation clearer

@DisplayName("A stack")
public class TestingAStackDemo {
​
    Stack<Object> stack;
​
    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew(a) {
        new Stack<>();
    }
​
    @Nested
    @DisplayName("when new")
    class WhenNew {
​
        @BeforeEach
        void createNewStack(a) {
            stack = new Stack<>();
        }
​
        @Test
        @DisplayName("is empty")
        void isEmpty(a) {
            assertTrue(stack.isEmpty());
        }
​
        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped(a) {
            assertThrows(EmptyStackException.class, () -> stack.pop());
        }
​
        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked(a) {
            assertThrows(EmptyStackException.class, () -> stack.peek());
        }
​
        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {
​
            String anElement = "an element";
​
            @BeforeEach
            void pushAnElement(a) {
                stack.push(anElement);
            }
​
            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty(a) {
                assertFalse(stack.isEmpty());
            }
​
            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped(a) {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }
​
            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked(a) { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); }}}}Copy the code

The result is as follows:

Repeat the test

Repeated testing is possible with the @repeattest annotation.

Example code is as follows:

public class RepeatTest {
    // Execute the test method 10 times
    @RepeatedTest(10)
    void repeatTest(a){
        System.out.println("Hello world!"); }}Copy the code

The result is as follows:

Conditions for testing

JUnit 5 supports condition testing. Common condition annotations are @enabledif, @disabledif, and built-in condition @enabledonos.

Example code is as follows:

public class IfTest {
​
    @Test
    @EnabledIf("getCondition")
    void enabledIfTest(a) {
        System.out.println("Hello world1");
    }
​
    @Test
    @DisabledIf("getCondition")
    void disabledIfTest(a){
        System.out.println("Hello world2");
    }
​
    boolean getCondition(a){
        return true; }}Copy the code

The result is as follows:

Parametric test

Parameterized testing is the execution of a test method using different parameters each time, with support for injection of array and Enum parameters, using annotations @Valuesource and EnumSource, respectively. Note that instead of using the @test annotation, the Test method needs to use the @parameterizedTest annotation.

Example code is as follows:

public class ParameterTest {
​
    @ParameterizedTest
    @ValueSource(strings = {"Lei Li","Meimei Han"})
    void parameterTest(String word){
        System.out.println("Hello,"+word); }}Copy the code

The result is as follows:

Advanced usage

Web service Testing

When annotating @SpringBooTtest on the test class, the SpringBoot environment will be automatically initialized during testing, and the Web service can be tested by instantiating the TestRestTemplate object.

Example code is as follows:

HelloController.java

@RestController
public class HelloController {
​
    @RequestMapping(value = "/hello",method = RequestMethod.GET)
    public Object hello(a){
        return "hello world!"; }}Copy the code

HelloControllerTests.java

// Indicates that the test environment is a random port
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerTests {
​
    @Autowired
    private TestRestTemplate restTemplate;
​
    @Test
    @DisplayName("Web service test demo")
    void helloTest(a){
        String object = restTemplate.getForObject("/hello", String.class);
        System.out.println(object);
        Assertions.assertEquals("hello world!",object); }}Copy the code

The test results are as follows:

Database testing

When the test method is annotated with @Transaction, the database operations in the test method will be rolled back by default without contaminating the database.

Example code is as follows:

package com.example.demo;
​
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.dto.User;
import com.example.demo.mapper.UserMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
​
​
/ * * *@description:
 * @time: 2021/8/4 16:15 * /
@SpringBootTest
public class DatabaseTest {
​
    @Autowired
    UserMapper userMapper;
​
    @Test
    @Transactional
    void databaseInsertTest(a){
        User entity = new User();
        entity.setUserName("San Zhang");
        entity.setUserId(2L);
        userMapper.insert(entity);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        User user = userMapper.selectById(3);
        Assertions.assertEquals(null,user); }}Copy the code

The result is as follows:

See no actual insert in database table.