33

Click here to load reader

Renaissance of JUnit - Introduction to JUnit 5

Embed Size (px)

Citation preview

Page 1: Renaissance of JUnit - Introduction to JUnit 5

Renaissance of JUnit - Introduction

to JUnit 5Jimmy Lu

[email protected] River, Inc.

Page 2: Renaissance of JUnit - Introduction to JUnit 5

Agenda• Why JUnit 5• JUnit Lambda• JUnit 5 architecture• JUnit the tool• JUnit the platform• Demo• Summary

Page 3: Renaissance of JUnit - Introduction to JUnit 5

Why Rewrite JUnit• JUnit 4.0 was released a decade ago• At the time of Java 5 and now java 9 is almost out

• Modularity - big ball of mud• Test discovery and execution - tightly coupled• Extensibility• Runner API

• Entire testing lifecycle – heavyweight• Only one runner allowed

• Rule API• limited to executing some code before, during, and after a test

was run

Page 4: Renaissance of JUnit - Introduction to JUnit 5

JUnit 4 modularity• Single junit.jar rules them all

Page 5: Renaissance of JUnit - Introduction to JUnit 5

JUnit Lambda• Crowdfunding

campaign• The code name of

JUnit 5• Main contributors:• Johannes Link • Marc Philipp• Matthias Merdes• Stefan Bechtold• Sam Brannen

Page 6: Renaissance of JUnit - Introduction to JUnit 5

JUnit 5 Overview• JUnit Platform + JUnit Jupiter + JUnit Vintage• JUnit Platform: TestEngine and Launcher APIs• JUnit Juptier: JUnit 5 TestEngine, new programming and

extension model• JUnit Vintage: TestEngine for running JUnit 3 & 4

• Modular• Extensible• Modern• Backward compatible

Page 7: Renaissance of JUnit - Introduction to JUnit 5

Architecture• JUnit the tool• APIs for writing tests

• JUnit the platform• TestEngines for running

tests• Launchers for

discovering and executing test via test engines

Page 8: Renaissance of JUnit - Introduction to JUnit 5

JUnit the ToolImproved Testing APIs

Page 9: Renaissance of JUnit - Introduction to JUnit 5

Annotations• @Test• @TestFactory• @DisplayName• @BeforeEach• @AfterEach• @BeforeAll• @AfterAll• @Nested

• @Tag• @Disabled• @ExtendWith

Page 10: Renaissance of JUnit - Introduction to JUnit 5

Meta-Annotations@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Test@Tag("fast")@interface Fast {}

@Fast void fastTest() { // test body}

Page 11: Renaissance of JUnit - Introduction to JUnit 5

Assertions• assertEquals(), assertNotNull(), etc.• assertThrows() and expectThrows• assertAll()• Message is now the last parameter• Lambda syntax

Page 12: Renaissance of JUnit - Introduction to JUnit 5

Assertions@Testvoid groupedAssertions() { assertAll("address", () -> assertEquals("John", address.getFirstName()), () -> assertEquals("User", address.getLastName()), () -> assertThrows(RuntimeException.class, () -> address.getZipcode()) );}

Page 13: Renaissance of JUnit - Introduction to JUnit 5

Assumptions• Abort test if assumptions fail.@Testvoid testOnlyOnCiServer() { assumeTrue("CI".equals(System.getenv("ENV"))); // remainder of test}

@Testvoid testOnlyOnDeveloperWorkstation() { assumeTrue("DEV".equals(System.getenv("ENV")), () -> "Aborting test: not on developer workstation"); // remainder of test}

Page 14: Renaissance of JUnit - Introduction to JUnit 5

Tagging and Filtering• Be used to filter test discovery and execution@Tag("fast")@Tag("model")class TaggingDemo {

@Test @Tag("taxes") void testingTaxCalculation() { }

}

Page 15: Renaissance of JUnit - Introduction to JUnit 5

Nested Tests• Express logical test group• No @BeforeAll and @AfterAll in nested testsclass OuterTest {

@Nested class InnerTest {

@BeforeEach void pushAnElement() { // test body } }}

Page 16: Renaissance of JUnit - Introduction to JUnit 5

Dependency Injection• No arguments are allowed in previous version of

JUnit in test methods• ParameterResolver• TestInfoParameterResolver• TestReporterParameterResolver

• Applied to test constructor, @Test, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll methods• Leveraged by extensions

Page 17: Renaissance of JUnit - Introduction to JUnit 5

Dependency Injection• TestInfoParameterResolver@BeforeEachvoid init(TestInfo testInfo) { String displayName = testInfo.getDisplayName(); assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()"));}

@Test@DisplayName("TEST 1")@Tag("my tag")void test1(TestInfo testInfo) { assertEquals("TEST 1", testInfo.getDisplayName()); assertTrue(testInfo.getTags().contains("my tag"));}

Page 18: Renaissance of JUnit - Introduction to JUnit 5

Dependency Injection• TestReporterParameterResolver@Testvoid reportSingleValue(TestReporter testReporter) { testReporter.publishEntry("a key", "a value");}

@Testvoid reportSeveralValues(TestReporter testReporter) { HashMap<String, String> values = new HashMap<>(); values.put("user name", "dk38"); values.put("award year", "1974");

testReporter.publishEntry(values);}

Page 19: Renaissance of JUnit - Introduction to JUnit 5

Dependency Injection• Inject Mockito mocks

@ExtendWith(MockitoExtension.class)class MyMockitoTest {

@BeforeEach void init(@Mock Person person) { when(person.getName()).thenReturn("Dilbert"); }

@Test void simpleTestWithInjectedMock(@Mock Person person) { assertEquals("Dilbert", person.getName()); }}

Page 20: Renaissance of JUnit - Introduction to JUnit 5

Interface Default Methods• Enables multiple inheritance in tests• @Test, @TestFactory, @BeforeEach, @AfterEachinterface EqualsContract<T> extends Testable<T> {

T createNotEqualValue();

@Test default void valueEqualsItself() { T value = createValue(); assertEquals(value, value); }}

Page 21: Renaissance of JUnit - Introduction to JUnit 5

Dynamic Tests• Test cases are generated at runtime• Via method annotated with @TestFactory

@TestFactoryStream<DynamicTest> dynamicTestsFromIntStream() { // Generates tests for the first 10 even integers. return IntStream .iterate(0, n -> n + 2) .limit(10) .mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0)));}

Page 22: Renaissance of JUnit - Introduction to JUnit 5

JUnit the PlatformAs the foundation of testing frameworks

Page 23: Renaissance of JUnit - Introduction to JUnit 5

Launcher API• Used by IDEs and build tools to launch the

framework• Central API for discovering and executing tests via

one or more engines• LauncherDiscoveryRequest• Slectors and filters• Return a TestPlan

• Feedback provided via the TestExecutionListener API

Page 24: Renaissance of JUnit - Introduction to JUnit 5

LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .selectors( selectPackage("com.example.mytests"), selectClass(MyTestClass.class) ) .filters(includeClassNamePattern(".*Test")) .build();

Launcher launcher = LauncherFactory.create();

TestPlan plan = launcher.discover(request);

TestExecutionListener listener = new SummaryGeneratingListener();launcher.registerTestExecutionListeners(listener);

launcher.execute(request);

Page 25: Renaissance of JUnit - Introduction to JUnit 5

TestEngine API• Test engine discovers and executes tests• for a particular programming model, E.g. Spock,

ScalaTest• Automatic discovery via Java’s ServiceLoader

mechanism (META-INF/services)• JupiterTestEngine (JUnit 5)• VintageTestEngine (JUnit 3/4)• Your own implementation

Page 26: Renaissance of JUnit - Introduction to JUnit 5

Extension Model• @ExtendWith(…)• Extension.class marker interface@ExtendWith({ MockitoExtension.class, FooExtension.class })@ExtendWith(BarExtension.class)class MyTestsV1 { // ...}

Page 27: Renaissance of JUnit - Introduction to JUnit 5

Extension Points• ContainerExecutionCondition• TestExecutionCondition• TestInstancePostProcessor• ParameterResolver• TestExecutionExceptionHandler• Before(All|Each|TestExecution)Callback• After(All|Each|TestExecution)Callback

Page 28: Renaissance of JUnit - Introduction to JUnit 5

Extension Pointsclass IgnoreIOExceptionExtension implements TestExecutionExceptionHandler {

@Override public void handleTestExecutionException( TestExtensionContext context, Throwable throwable) throws Throwable { if (throwable instanceof IOException) { return; } throw throwable; }}

Page 29: Renaissance of JUnit - Introduction to JUnit 5

Spring Extensionprivate DemoBean demoBean;

@Autowiredpublic void setDemoBean(DemoBean demoBean) { this.demoBean = demoBean;}

@Testvoid testBean() { Assertions.assertNotNull(demoBean);}

Page 30: Renaissance of JUnit - Introduction to JUnit 5

API Annotations• @API• Internal• Deprecated• Experimental• Maintained• Stable

@API(Experimental)public interface TestExecutionListener { // code body}

Page 31: Renaissance of JUnit - Introduction to JUnit 5

DemoAssertions.assertTrue(demo.isSuccessful())

Page 32: Renaissance of JUnit - Introduction to JUnit 5

Summary• Java 8 support• Improved testing API• Improved modularity - separate of concern• Easier to maintain and extend• Backward compatible • Aimed to be the foundation of java testing

ecosystem

Page 33: Renaissance of JUnit - Introduction to JUnit 5

Reference• JUnit 5 user guide• JUnit 5 - An Early Test Drive – part1, part2• JUnit 5 - from Lambda to Alpha and beyond• JUnit 5 - Shaping the Future of Testing on the JVM