📜 ⬆️ ⬇️

10 interesting innovations in JUnit 5

Last Sunday, Sam Brannen announced the release of JUnit 5 ! Hooray!


Congratulations to all the participants @JUnitTeam as well as all those who use JUnit in their work! Let's see what we have prepared in this release.

Content


0. Introduction
1. Getting started
2. Review of innovations
2.1. public - everything
2.2. Advanced assert
2.3. Work with exceptions
2.4. New Test
2.5. New basic annotations
2.6. Nested classes
2.7. Shared instance of the class to run tests
2.8. Automatic restart of the test
2.9. Parameterized Tests
2.10. Annotated default methods in interfaces
3. Conclusion

1. Introduction


So, the official site starts with the fact that it tells us about the new structure of JUnit:
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage (← off.site ).

The JUnit Platform is the fundamental basis for running JVM frameworks for testing. The platform provides TestEngine API for developing frameworks (for testing) that can be run on the platform. In addition, the platform has Console Launcher for launching the platform from the command line as well as for launching any JUnit 4 Runner on the platform. By the way, there are already plugins for Gradle and Maven .
')
JUnit Jupiter is the heart of JUnit 5. This project provides new opportunities for writing tests and creating your own extensions. The project implemented a special TestEngine to run tests on the previously described platform.

JUnit Vintage - Legacy support. It is determined by TestEngine to run tests targeted at JUnit 3 and JUnit 4.

1. Getting started


There are already plenty of examples on the Internet for setting up Gradle and Maven projects. The JetBrains blog has a separate post about setting up JUnit 5 in IDEA .

2. Review of innovations


And now let's move on to the examples!

2.1. public - everything
JUnit no longer requires public methods.

@Test void test() { assertEquals("It " + " works!" == "It works!"); } 


2.2. Advanced assert
The optional message was made the last argument.

 assertEquals(2017, 2017, "The optional assertion message is now the last parameter."); 

In the fifth version, you can use Supplier <String> to construct the message.

 assertTrue("habr" == "habr", () -> "Assertion messages can be lazily evaluated"); 

Added a special method for the logical grouping of tests.

 //      , //  -      assertAll("habr", () -> assertThat("https://habrahabr.ru", startsWith("https")), () -> assertThat("https://habrahabr.ru", endsWith(".ru")) ); 

There was a method for working with Iterable.

 assertIterableEquals(asList(1, 2, 3), asList(1, 2, 3)); 

Added an interesting method for comparing a rowset. Regular expressions are supported!

 Assertions.assertLinesMatch( asList("  ", "   regex: \\d{2}\\.\\d{2}\\.\\d{4}"), asList("  ", "   regex: 12.09.2017") ); 

2.3. Work with exceptions
Dealing with exceptions has become more linear.

 Throwable exception = assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("-   "); }); assertEquals("-   ", exception.getMessage()); 

2.4. New Test
JUnit 5 introduced the new Test annotation, which is in the org.junit.jupiter.api.Test package. Unlike the fourth version, the new annotation serves only as a marker.

See the differences
 // JUnit 4 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Test { Class<? extends Throwable> expected() default Test.None.class; long timeout() default 0L; public static class None extends Throwable { private static final long serialVersionUID = 1L; private None() { } } } 

The new annotation looks like this.

 // JUnit 5 @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(Stable) @Testable public @interface Test { } 

2.5. New basic annotations
The fifth version added new basic annotations.

See a good example.
 import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class StandardTests { //  @BeforeClass @BeforeAll static void initAll() { } //  @Before @BeforeEach void init() { } @Test void succeedingTest() { } @Test void failingTest() { fail("a failing test"); } //  @Ignore @Test @Disabled("for demonstration purposes") void skippedTest() { // not executed } //         . @DisplayName("╯°□°)╯") void testWithDisplayNameContainingSpecialCharacters() {} //  @After @AfterEach void tearDown() { } //  @AfterClass @AfterAll static void tearDownAll() { } } 

2.6. Nested classes
The @Nested annotation allows the use of inner classes in test design, which sometimes allows for a more convenient way to group / supplement tests.

An example from official documentation.
 import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.EmptyStackException; import java.util.Stack; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @DisplayName("A stack") class TestingAStackDemo { Stack<Object> stack; @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew() { new Stack<>(); } @Nested @DisplayName("when new") class WhenNew { @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test @DisplayName("is empty") void isEmpty() { assertTrue(stack.isEmpty()); } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped() { assertThrows(EmptyStackException.class, () -> stack.pop()); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { assertThrows(EmptyStackException.class, () -> stack.peek()); } @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element"; @BeforeEach void pushAnElement() { stack.push(anElement); } @Test @DisplayName("it is no longer empty") void isNotEmpty() { assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped() { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked() { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } } 

2.7. Shared instance of the class to run tests
To guarantee the independence and isolation of tests, JUnit in all previous versions has always created an instance per test (i.e., a separate instance for each method launch). In the fifth version, this behavior can be changed using the new @TestInstance annotation (Lifecycle.PER_CLASS). In this case, the instance will be created only once and will be reused to run all the tests defined inside this class.

2.8. Automatic restart of the test
Another nice addition! The @RepeatedTest annotation tells JUnit to run this test several times. At the same time, each such call will be an independent test, which means that the annotations @BeforeAll, @BeforeEach, @AfterEach and @AfterAll will work for it.

 @RepeatedTest(5) void repeatedTest() { System.out.println("     . "); } 

It is worth noting that you can configure an additional display of information about the test runs. For example, show the launch number. Special constants defined inside the same annotation are responsible for this.

2.9. Parameterized Tests
Parameterized tests allow you to run a test several times with various inputs. Currently, only data of primitive types is supported: int , long , double , String . But do not despair! JUnit 5 defines several additional annotations for specifying a data source for parameterized tests. So, let's begin!

 @ParameterizedTest @ValueSource(strings = { "Hello", "World" }) void testWithStringParameter(String argument) { assertNotNull(argument); } 

Another inspiring example with @ValueSource.

 @ParameterizedTest @ValueSource(strings = { "01.01.2017", "31.12.2017" }) void testWithConverter(@JavaTimeConversionPattern("dd.MM.yyyy") LocalDate date) { assertEquals(2017, date.getYear()); } 

Example with CSV parsing.

 @ParameterizedTest @CsvSource({ "foo, 1", "bar, 2", "'baz, qux', 3" }) //   : @CsvFileSource(resources = "/two-column.csv") void testWithCsvSource(String first, int second) { assertNotNull(first); assertNotEquals(0, second); } 

An example with Enum.

 @ParameterizedTest @EnumSource(value = TimeUnit.class, names = { "DAYS", "HOURS" }) void testWithEnumSourceInclude(TimeUnit timeUnit) { assertTrue(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit)); } 

Example with data source.

 @ParameterizedTest @ArgumentsSource(MyArgumentsProvider.class) void testWithArgumentsSource(String argument) { assertNotNull(argument); } static class MyArgumentsProvider implements ArgumentsProvider { @Override public Stream<? extends Arguments> provideArguments(ExtensionContext context) { return Stream.of("foo", "bar").map(Arguments::of); } } 

Even more cool examples can be found on the official website in section 3.13. Parameterized Tests .

2.10. Annotated default methods in interfaces
JUnit now knows how to work with default methods in interfaces! Here is one of the official examples of the application of this innovation. I suggest to look at an interesting example with Equals Contract.

 public interface Testable<T> { T createValue(); } 

 public interface EqualsContract<T> extends Testable<T> { T createNotEqualValue(); @Test default void valueEqualsItself() { T value = createValue(); assertEquals(value, value); } @Test default void valueDoesNotEqualNull() { T value = createValue(); assertFalse(value.equals(null)); } @Test default void valueDoesNotEqualDifferentValue() { T value = createValue(); T differentValue = createNotEqualValue(); assertNotEquals(value, differentValue); assertNotEquals(differentValue, value); } } 

Conclusion


It's great that the popular testing framework is decided on such serious experiments with the API and tries to keep up with the times!

Finally leave a couple of links: the official site JUnit 5 and a very friendly guide .

A lot of interesting things remain outside the scope of this article. For example, a separate review is worth the extension mechanism provided by JUnit 5.
Thanks for attention!

Happy coding!

Source: https://habr.com/ru/post/337700/


All Articles