This is the fourth day of my participation in Gwen Challenge

This article is participating in “Java Theme Month – Java Development in Action”, see the activity link for details


junit5

JUnit5 was released in 2017. Are you still using junit4?

What is a junit5

Unlike previous JUnit versions, JUnit 5 consists of multiple different modules from three different subprojects.

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

The JUnit Platform provides the foundation for starting the testing framework on the JVM. It also defines the TestEngine API for developing test frameworks to run on the platform. In addition, the platform provides a console launcher for starting the platform from the command line, and provides for Gradle and Maven to build plugins to [JUnit 4-based Runner for running arbitrary TestEngines on the platform.

JUnit Jupiter is a combination of a new programming model and [extension Model][] for writing tests and extensions in JUnit 5. The Jupiter subproject provides TestEngine for running Jupite-based tests on the platform.

JUnit Vintage provides TestEngine for running JUnit 3 – and JUnit 4-based tests on the platform.

Why JUnit 5

As the Java unit testing world matures with testing frameworks like JUnit, developers expect more from unit testing frameworks: more ways to test, less dependence on other libraries.

In anticipation of a more powerful testing framework, JUnit, a leader in Java testing, has released JUnit 5 with the following features:

  • Provides new assertions and test annotations to support test class embedding
  • Richer testing methods: support dynamic testing, repeated testing, parameterized testing and so on
  • Modularization is implemented to decoupled different modules such as test execution and test discovery and reduce dependencies
  • Provides support for Java 8, such as Lambda expressions, Sream APIS, and more.

The basic annotation

** @test :** indicates that a method is a Test method. But unlike JUnit4’s @test, whose responsibilities are very simple and cannot declare any attributes, extended tests will be provided by Jupiter with additional tests

** @parameterizedtest :** indicates that the method is a ParameterizedTest

** @REPEATedtest :** indicates that the method can be executed repeatedly

** @displayname :** sets the DisplayName for the test class or test method

** @beforeeach :** indicates execution BeforeEach unit test

** @aftereach :** indicates execution AfterEach unit test

** @beforeall :** indicates execution BeforeAll unit tests

** @afterall :** indicates execution AfterAll unit tests

** @tag :** represents a unit test category, similar to @categories in JUnit4

** @disabled :** Indicates that the test class or test method is not executed, similar to @ignore in JUnit4

** @timeout :** Indicates that the test method will return an error if it runs for longer than the specified time

** @extendWith :** Provides extended class references for test classes or test methods

Common annotation format:

class StandardTests {

    // Similar to junit4's @beforeClass, each test class is run once
    @BeforeAll
    static void initAll(a) {}// Similar to @before in junit4, each test case is run once
    @BeforeEach
    void init(a) {}@Test
    @displayName (" Test successful ")
    void succeedingTest(a) {}@Test
    @displayName (" Failed test ")
    void failingTest(a) {
        fail("a failing test");
    }

    // Disable test cases
    @Test
    @Disabled("for demonstration purposes")
    void skippedTest(a) {
        // not executed
    }

    @Test
    void abortedTest(a) {
        assumeTrue("abc".contains("Z"));
        fail("test should have been aborted");
    }


    // Corresponding to @beforeeach, each test class is executed once, generally used to recover the environment
    @AfterEach
    void tearDown(a) {}// Corresponding to @beforeall, each test class is executed once, generally used to recover the environment
    @AfterAll
    static void tearDownAll(a) {}}Copy the code

New features

The display name

@displayname (" DisplayName test ")
class DisplayNameDemo {

    @Test
    @displayName (" My first test case ")
    void testWithDisplayNameContainingSpaces(a) {}@Test
    @ DisplayName (" ╯ ╯ ° / °) ")
    void testWithDisplayNameContainingSpecialCharacters(a) {}@Test
    @ DisplayName (" 😱 ")
    void testWithDisplayNameContainingEmoji(a) {}}Copy the code

IDE run test results show:

** Advantages: ** This way, Chinese explanations can be added in cases where method names are too long in English or difficult to describe clearly in English

More powerful assertions

JUnit Jupiter provides many of the assertion methods that JUnit4 already has, and adds some that are suitable for use with Java 8 lambda. All JUnit Jupiter Assertions are [org. JUnit. Jupiter. Assertions] in the class the static method.

Group assertion:

The assertion is successful only when multiple conditions are met

@Test
void groupedAssertions(a) {
    Person person = new Person();

    Assertions.assertAll("person",
                         () -> assertEquals("niu", person.getName()),
                         () -> assertEquals(18, person.getAge())
                        );
}
Copy the code

Exception assertion:

Rule is required for Junit4, which provides more elegant exception assertions for assertThrows

@Test
void exceptionTesting(a) {
    Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
        throw new IllegalArgumentException("a message");
    });
    assertEquals("a message", exception.getMessage());
}
Copy the code

Timeout assertion:

@Test
@displayName (" Timeout test ")
public void timeoutTest(a) {
    Assertions.assertTimeout(Duration.ofMillis(100), () -> Thread.sleep(50));
}
Copy the code

Labeling and filtering

By grouping tests together with labels, perform different logical tests at different stages, such as fast smoke tests and slow but important tests

@Test
@Tag("fast")
	void testing_faster(a) {}@Test
@Tag("slow")
	void testing_slow(a) {}Copy the code

Then configure the Maven-Surefire-plugin

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22. 0</version>
    <configuration>
        <properties>
            <includeTags>fast</includeTags>
            <excludeTages>slow</excludeTages>
        </properties>
    </configuration>
</plugin>
Copy the code

Nested test

As the number of classes and code we write grows, so does the number of corresponding test classes we need to test.

To address the problem of exploding test classes, JUnit 5 provides the @nested annotation to logically group test case classes in the form of static inner member classes.

And each static inner class can have its own lifecycle methods, which are executed hierarchically from the outside in.

Also, nested classes can be tagged with @DisplayName so that we can use the correct test name. Here’s a simple way to use it:

@DisplayName("A stack")
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());
        }


        @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()); }}}}Copy the code

Junit has no limit on the number of nesting layers, and it is generally not recommended to use more than three unless necessary, as an overly complex hierarchy can make it more difficult for developers to understand use case relationships

Dependency injection of constructors and methods

In all previous versions of JUnit, test constructors or methods were not allowed to take arguments (at least not using standard Runner implementations). As one of JUnit Jupiter’s major changes, test constructors and methods are now allowed to have arguments. This brings greater flexibility and enables dependency injection for constructors and methods

  • TestInfo You can obtain test information
  • TestReporter can output information to the console
@Test
@DisplayName("test-first")
@Tag("my-tag")
void test1(TestInfo testInfo) {
    assertEquals("test-first", testInfo.getDisplayName());
    assertTrue(testInfo.getTags().contains("my-tag"));
}

@Test
@DisplayName("test-second")
@Tag("my-tag")
void test2(TestReporter testReporter) {
    testReporter.publishEntry("a key"."a value");
}
Copy the code

Repeat the test

Call the same test case multiple times

@RepeatedTest(10)
@displayName (" Repeat test ")
public void testRepeated(a) {
    / /...
}
Copy the code

Dynamic testing

Dynamic testing requires only one piece of code to validate various types of inputs and outputs at once

@TestFactory
@displayName (" dynamic test ")
Stream<DynamicTest> dynamicTests(a) {
    List<Person> persons = getAllPerson();

    return persons.stream()
        .map(person -> DynamicTest.dynamicTest(person.getName() + "-test", () -> assertTrue(person.getName().contains("niu"))));
}
Copy the code

Timeout test

To verify that a use case has timed out, it is generally required that a single unit test should not exceed 1 second

class TimeoutDemo {
    @BeforeEach
    @Timeout(5)
    void setUp(a) {
        // fails if execution time exceeds 5 seconds
    }

    @Test
    @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS)
    void failsIfExecutionTimeExceeds1000Milliseconds(a) {
        // fails if execution time exceeds 1000 milliseconds
        AssertTimeout (duration.ofmillis (1000), () -> thread.sleep (1500));}}Copy the code

Parameter test

Parameter testing is one of the most useful features in my opinion, reducing the amount of repetitive template code, and is one of junit5’s most amazing enhancements and highly recommended

ValueSource: Specifies an input source for parameterized tests. Supports eight base classes as well as String and Class

NullSource: provides a null input parameter for parameterized tests

@enumSource: indicates providing an enumeration input parameter to parameterized tests

@csvSource: reads the content in CSV format as the parameter of the parameterized test

@csvfilesource: reads the content of a specified CSV file as an input parameter to a parameterized test

@methodSource: reads the return value of the specified method as a parameterized test parameter (note that the method return needs to be a stream)

ArgumentsSource: Specifies a custom, reusable ArgumentsProvider.

After reading the description, I loved it

One is three basic test cases

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@displayName (" Parameterized test 1")
public void parameterizedTest1(String string) {
    assertTrue(StringUtils.isNotBlank(string));
}
Copy the code

If it is not an underlying type, you can use method constructs as long as the return value is of type Stream and Arguments instance streams are used for multiple Arguments

@ParameterizedTest
@MethodSource("method")
@displayName (" method source argument ")
public void testWithExplicitLocalMethodSource(String name) {
    Assertions.assertNotNull(name);
}

private static Stream<String> method(a) {
    return Stream.of("apple"."banana");
}
Copy the code

@csvSource allows you to represent argument lists as comma-separated values (for example, string literals)

@ParameterizedTest
@CsvSource({"steven,18", "jack,24"})
@displayName (" Parameterized test - CSV format ")
public void parameterizedTest3(String name, Integer age) {
    System.out.println("name:" + name + ",age:" + age);
    Assertions.assertNotNull(name);
    Assertions.assertTrue(age > 0);
}
Copy the code

@csvfilesource Uses a CSV file in classpath. Each line in the CSV file causes a call to the parameterized test

This completely isolates the test data from the test method for better decoupling

@ParameterizedTest
@CsvFileSource(resources = "/persons.csv")  // Specify the CSV file location
@displayName (" Parameterized test - CSV file ")
public void parameterizedTest2(String name, Integer age) {
    System.out.println("name:" + name + ",age:" + age);
    Assertions.assertNotNull(name);
    Assertions.assertTrue(age > 0);
}
Copy the code

If the requirements still do not meet, you can use @ArgumentsSource to customize your own data source. The source must be encapsulated to fetch DATA such as JSON or XMl

AssertJ

Once you have defined the test method you want to run, the next step is to focus on the details of the test method, which involves assertions and assumptions

Assertion: Encapsulates common judgment logic so that when a condition is not met, the test case is considered to have failed

Hypothesis: Similar to assertions, when a condition is not met, the test simply exits rather than being judged a failure

Assertions are most commonly used because they do not affect subsequent test cases

In addition to Junit5’s built-in assertions, AssertJ is a very useful assertion tool that features streaming assertions, similar to how Java8 uses them

@Test
void testString(a) {
    Asserts null or an empty string
    assertThat("").isNullOrEmpty();
    // Assert an empty string
    assertThat("").isEmpty();
    // Assert string equality Assertion ignores case to determine string equality
    assertThat("niu").isEqualTo("niu").isEqualToIgnoringCase("NIu");
    // Assert the start string end character through the length of the string
    assertThat("niu").startsWith("ni").endsWith("u").hasSize(3);
    // Assert contains string does not contain string
    assertThat("niu").contains("iu").doesNotContain("love");
    // The assertion string occurs only once
    assertThat("niu").containsOnlyOnce("iu");
}

@Test
void testNumber(a) {
    // assert equality
    assertThat(42).isEqualTo(42);
    // Assert greater than or equal to
    assertThat(42).isGreaterThan(38).isGreaterThanOrEqualTo(38);
    // Assert is less than or equal to
    assertThat(42).isLessThan(58).isLessThanOrEqualTo(58);
    / / assertion 0
    assertThat(0).isZero();
    // Assert positive and non-negative numbers
    assertThat(1).isPositive().isNotNegative();
    // Assert that negative numbers are not positive
    assertThat(-1).isNegative().isNotPositive();
}

@Test
void testCollection(a) {
    // The assertion list is empty
    assertThat(newArrayList()).isEmpty();
    // Assert the start and end elements of the list
    assertThat(newArrayList(1.2.3)).startsWith(1).endsWith(3);
    // The assertion list contains elements and is sorted
    assertThat(newArrayList(1.2.3)).contains(1, atIndex(0)).contains(2, atIndex(1)).contains(3)
        .isSorted();
    // The assertion is included with the given list
    assertThat(newArrayList(3.1.2)).isSubsetOf(newArrayList(1.2.3.4));
    // Assert that there is a unique element
    assertThat(newArrayList("a"."b"."c")).containsOnlyOnce("a");
}

@Test
void testMap(a) {
    Map<String, Object> foo = ImmutableMap.of("A".1."B".2."C".3);

    // Assert that map is not empty size
    assertThat(foo).isNotEmpty().hasSize(3);
    // Assert that map contains elements
    assertThat(foo).contains(entry("A".1), entry("B".2));
    // Assert that map contains key
    assertThat(foo).containsKeys("A"."B"."C");
    // Assert that map contains value
    assertThat(foo).containsValue(3);
}
// Other assertions, please explore......
Copy the code

Think about how we would have written assertions without AssertJ. Would it have required multiple Asserts

AssertJ’s assertion code is much cleaner, and streaming assertions complement Junit’s assertion methods by taking advantage of anonymous methods and stream types since Java8.

reference

Junit.org/junit5/docs…

assertj.github.io/doc/