renaissance of junit - introduction to junit 5

Post on 12-Apr-2017

313 Views

Category:

Software

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Renaissance of JUnit - Introduction

to JUnit 5Jimmy Lu

jilu@digitalriver.comDigital River, Inc.

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

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

JUnit 4 modularity• Single junit.jar rules them all

JUnit Lambda• Crowdfunding

campaign• The code name of

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

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

Architecture• JUnit the tool• APIs for writing tests

• JUnit the platform• TestEngines for running

tests• Launchers for

discovering and executing test via test engines

JUnit the ToolImproved Testing APIs

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

• @Tag• @Disabled• @ExtendWith

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

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

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

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

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}

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

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

}

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

@Nested class InnerTest {

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

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

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"));}

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);}

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()); }}

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); }}

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)));}

JUnit the PlatformAs the foundation of testing frameworks

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

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);

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

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

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

Extension Pointsclass IgnoreIOExceptionExtension implements TestExecutionExceptionHandler {

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

Spring Extensionprivate DemoBean demoBean;

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

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

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

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

DemoAssertions.assertTrue(demo.isSuccessful())

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

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

top related