[JAVA] Changes in JUnit5M4-> M5

This is my first Qiita.

Taste of JUnit 5 at KANJAVA PARTY 2017 !!! held on June 24, 2017 / junit5falsewei-jian) was announced under the title. The version at the time of announcement was M4, but there are some changes in M5, so I will follow you.

However, since we mainly describe JUnit Jupiter here, please refer to the original JUnit 5 User Guide for other information. Please refer to it.

All jar files have a Manifest attribute called "Automatic-Module-Name"

It is compatible with Jigsaw. Jigsaw isn't very familiar with it, so I'll avoid it.

The @ParameterizedTest parameter now only applies to test methods

It's a little confusing. Write the code. In JUnit5M4, since there is an argument at the method callback of setup1, I try to apply the parameter of @ParameterrizedTest, but it fails because the types do not match. Instead, I was able to receive the parameters of ParameterizedTest in the lifecycle callback method like setup2. In JUnit5M5, the parameter of @ParameterizedTest is not applied to the life cycle callback method, so on the contrary, an error will occur at the time of callback of setup2. This fix is to prevent strange problems when @ParameterizedTest and normal @Test are mixed in one test class.

ParameterizedNGTest.java


public class ParameterizedNGTest {
	@BeforeEach
	void setUp1(TestInfo info) {
	}

	@BeforeEach
	void setUp2(String p1, String p2) {
		System.out.println(p1 + p2);
	}
	
	@ParameterizedTest
	@CsvSource({ "x, 1", "y, 2","z, 3" })
	void testIt(String input, String expected) {
	}
}

The artifact name "junit-jupiter-migration-support" has been changed to "junit-jupiter-migrationsupport"

Please be careful when acquiring with Maven or Gradle.

The following API has changed

Class names are omitted for items whose class does not change.

Old API New API Remarks
ParameterResolver#supports() supportsParameter()
ParameterResolver#resolve() resolveParameter()
ContainerExecutionCondition#evaluate() ExecutionCondition#evaluateExecutionCondition() ContainerExecutionCondition is abolished
TestExecutionCondition#evaluate() ExecutionCondition#evaluateExecutionCondition() TestExecutionCondition is abolished
TestExtensionContext#getTestException() ExtensionContext#getExecutionException() TestExtensionContext is abolished
TestExtensionContext#getTestInstance() ExtensionContext#getTestInstance() TestExtensionContext is abolished. The return value is also Object-> Optional<Object>Change to
TestTemplateInvocationContextProvider#supports() supportsTestTemplate()
TestTemplateInvocationContextProvider#resolve() provideTestTemplateInvocationContexts()
ArgumentsProvider#arguments() provideArguments()
ObjectArrayArguments#create() Arguments#of() ObjectArrayArguments obsolete
@MethodSource#names value

Introduced @TestInstance

The @TestInstance annotation is an annotation for controlling the life cycle of a test class. Until now, JUnit has materialized a test class each time a test method is executed.

TestInstancePerMethodTest.java


@RunWith(JUnitPlatform.class)
public class TestInstancePerMethodTest {
	private int i = 0;
	
	@Test
	void test1(){System.out.println(i++);}
	@Test
	void test2(){System.out.println(i++);}
}

Execution result.


0
0

If you give @TestInstance (and specify Lifecycle.PER_CLASS), the test class will be materialized only once and the result will be different.

TestInstancePerClassTest.java


@RunWith(JUnitPlatform.class)
@TestInstance(Lifecycle.PER_CLASS)
public class TestInstancePerClassTest {
	private int i = 0;
	
	@Test
	void test1(){System.out.println(i++);}
	@Test
	void test2(){System.out.println(i++);}
}

Execution result.


0
1

By giving @TestInstance, the execution cost of the test class will be reduced, but it should be noted that the initialization process must be performed by @BeforeEach, not at the time of initialization.

At the same time, it should be noted that the story is a little complicated because @BeforeAll and @AfterAll must be specified in a way that matches the materialization of the test class.

In other words, if you specify PER_CLASS, you will get a run-time error unless you annotate @BeforeAll or @AfterAll to the dynamic method instead of the static method.

Also, if you use @TestInstance and @Nested together, you can write @BeforeAll and @AfterAll even for nested test classes, so it may be better to use them properly. It may be.

By the way, if you use @TestInstance, the order of life cycle callbacks will change. CallBackOrderTest.java The output result when is executed as it is is as shown in ①, but when @TestInstance is annotated, it is as shown in ②. I think it's a hassle, but if you think about it normally, that's the result.

①.


ExecutionCondition:false
beforeAll
postProcessTestInstance
ExecutionCondition:true
beforeEach
beforeTestExecution
foo
handleTestExecutionException
afterTestExecution
afterEach
afterAll

②.


postProcessTestInstance
ExecutionCondition:false
beforeAll
ExecutionCondition:true
beforeEach
beforeTestExecution
foo
handleTestExecutionException
afterTestExecution
afterEach
afterAll

The specification of assertAll has changed

Until JUnit5M4, when an exception occurred in each Executable without assertAll, the subsequent Executable was treated as an error without evaluation, but after M5, an exception other than the blacklist exception occurred. But the evaluation is no longer interrupted. The execution result of M4 and M5 when there is the following code is shown below.

AssertAllInExceptionTest.java


@RunWith(JUnitPlatform.class)
public class AssertAllInExceptionTest {
	@Test
	void thrownNullPointerException() {
		assertAll(
				() -> {throw new NullPointerException();},
				() -> assertEquals(2, 3)
		);
	}
}
M4 execution result Execution result of M5
Screenshot 2017-07-13 13.09.51.png Screenshot 2017-07-13 13.14.23.png

You can see that M4 is evaluated as Error, M5 is evaluated as Failure, and the second Executable is evaluated.

Later, I used the word "blacklist exception" earlier, but there is a question that "that's what it is!". In the User Guide, the word "blacklisted exception" was mentioned in the part of the announcement of the specification change of assertAll and the release note of M2 in the other part.

If the exception is a blacklisted exception such as an OutOfMemoryError, however, it will be rethrown.

The answer to the question "What is such as (etc.) !! What else is there !!" was not in the User Guide but in the code.

[BlacklistedExceptions.java](https://github.com/junit-team/junit5/blob/r5.0.0-M4/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ Looking at the code called BlacklistedExceptions.java), it seems that it is currently only OutOfMemoryError.

@TestTemplate has been documented

It seems that it was from JUnit5M4, but I overlooked it because it was not written in the User Guide. There was a description in the User Guide of JUnit5M5, so I will write an overview here.

I think @TestTemplate, like @ParameterizedTest, is for separating test code and test data. I found out by reading the code, but since @ParameterizedTest is an annotation that annotates @TestTemplate, I think that @TestTemplate should not be a concern if you are not dissatisfied with @ParameterizedTest. I will.

The following is a slightly modified version of the User Guide machine translation.

3.14. Test Templates

The @TestTemplate method is a template for test cases, not regular test cases. Therefore, it is designed to be called multiple times, depending on the number of call contexts returned by the registered provider. Therefore, @TestTemplate should be used in conjunction with an Extension that implements TestTemplateInvocationContextProvider. Invoking a test template method behaves like running a normal @ Test method and fully supports the same lifecycle callbacks and extensions. For usage examples, see Providing Invocation Contexts for Test Templates.

5.8. Providing Invocation Contexts for Test Templates

The @TestTemplate method can only be executed if at least one TestTemplateInvocationContextProvider is registered. Each of these providers provides a stream of TestTemplateInvocationContext instances. Each context can specify a custom display name and a list of additional extensions to use only in the next call to the @TestTemplate method. The following example describes a test template and shows how to register and implement a TestTemplateInvocationContextProvider.

@TestTemplate
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
void testTemplate(String parameter) {
    assertEquals(3, parameter.length());
}

static class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
    @Override
    public boolean supportsTestTemplate(ExtensionContext context) {
        return true;
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
        return Stream.of(invocationContext("foo"), invocationContext("bar"));
    }

    private TestTemplateInvocationContext invocationContext(String parameter) {
        return new TestTemplateInvocationContext() {
            @Override
            public String getDisplayName(int invocationIndex) {
                return parameter;
            }

            @Override
            public List<Extension> getAdditionalExtensions() {
                return Collections.singletonList(new ParameterResolver() {
                    @Override
                    public boolean supportsParameter(ParameterContext parameterContext,
                            ExtensionContext extensionContext) {
                        return parameterContext.getParameter().getType().equals(String.class);
                    }

                    @Override
                    public Object resolveParameter(ParameterContext parameterContext,
                            ExtensionContext extensionContext) {
                        return parameter;
                    }
                });
            }
        };
    }
}

In this example, the test template is called twice. The display name of the call will be the "foo" and "bar" specified in the call context. Each call registers a custom ParameterResolver that will be used to resolve the method parameters. The output when using ConsoleLauncher is:

└─ testTemplate(String) ✔
   ├─ foo ✔
   └─ bar ✔

The TestTemplateInvocationContextProvider extension API primarily allows you to prepare test class instances separately with different contexts—for example, with different parameters, or create multiple times without changing the context. Tests in — Mainly used to implement various tests that rely on repeated calls to similar methods.

Made human readable display name when @ParameterizedTest accepts an array as an argument

Probably in response to this Issue, if you try to simply output an array object in Java [Ljava.lang.String; @ 7c29daf3 I think I fixed the fact that I couldn't see the contents.

However, I couldn't verify it because I couldn't write a little code that can be compared between M4 and M5. Excuse me.

The specification of @EnumSource has changed

In M4, the names of @ EnumSource specified the Enum name to be acquired, but with the addition of the property called mode in M5, the meaning of the information passed to names can be changed. It was.

Mode Meaning of names
INCLUDE Default value. Applies only to Enum names specified in names
EXCLUDE Applies only to Enum names not specified in names
MATCH_ALL Applies only Enum names that match all the regular expressions specified in names
MATCH_ANY Applies only Enum names that match any of the regular expressions specified in names

(Soliloquy) Do you want to use it that much?

The specification of @MethodSource has changed

It is no longer an error to specify DoubleStream, IntStream, LongStream as the return value of the method specified by @MethodSource.

The specification of @TestFactory has changed

@TestFactory now supports any nested dynamic container. For more information, see DynamicContainer and the abstract-based DynamicNode. See junit5 / docs / current / api / org / junit / jupiter / api / DynamicNode.html).

It is that. A sample is also attached to the User Guide.

    @TestFactory
    Stream<DynamicNode> dynamicTestsWithContainers() {
        return Stream.of("A", "B", "C")
            .map(input -> dynamicContainer("Container " + input, Stream.of(
                dynamicTest("not null", () -> assertNotNull(input)),
                dynamicContainer("properties", Stream.of(
                    dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),
                    dynamicTest("not empty", () -> assertFalse(input.isEmpty()))
                ))
            )));
    }

Looking at this, I somehow became able to see how to utilize Dynamic Test. Is DynamicTest a mechanism that makes it easy to translate tests written by an external DSL into a structured set of test code so that it can be reported properly?

The AfterAllCallback implementation interface can now catch exceptions raised by @BeforeAll.

Strictly speaking, it seems to be an exception that occurred in the method that annotated @BeforeAll or the BeforeAllCallback interface implementation method.

I wasn't aware of it, but it means that I couldn't do it with M4. I looked it up, but in M4 the argument of AfterAllCallback # afterAll was ContainerExtensionContext, and there was no API to get the exception that occurred. From M5, the Container Extension Context has been integrated into the Extension Contedxt so it can be captured.

In the same theory, exceptions thrown by @BeforeEach can be caught by the AfterEachCallback implementation interface. (Available at M4)

Information can now be shared between test classes via the Store

Yeah, I'm not sure.

Extensions may now share state across top-level test classes by using the Store of the newly introduced engine-level ExtensionContext.

However, there are too few explanations about the Store ...

API maturity

Only the ones that are different / new from the time of the M4 document, but they are as follows.

Old maturity New maturity
@TestInstance - Undescribed
@TestTemplate - Experimental
Assertions#assertAll Maintained Experimental
Assumptions#assumingThat Maintained Experimental
DynamicContainer - Experimental
DynamicNode - Experimental
ExecutionCondition - Experimental
TestTemplateInvocationContextProvider - Experimental
TestTemplateInvocationContext - Experimental

Basically, there are only omissions or additions, and it may not change for each milestone.

Is it like this?

Looking at the release notes of the User Guide, the main changes other than Chapter 7 are as follows.

Finally

I haven't had many opportunities to announce recently, so I'll do my best for JUnit 5. We hope to keep you informed of the changes, at least until the official release.

The next milestone is M6, but as of July 14, 2017, it is scheduled to be released on July 16, 2017, and the progress seems to be 42%, so it will probably be delayed w

Recommended Posts

Changes in JUnit5M4-> M5
Changes in Mockito 2
Changes in mybatis-spring-boot-starter 2.0
Changes in mybatis-spring-boot-starter 2.1
Changes in mybatis-spring-boot-starter 1.3
Changes in mybatis-spring-boot-starter 1.2
Core Location changes in iOS 14
Major changes in Spring Boot 1.5
Major changes in Spring Framework 5.0 core functionality