Renaissance of JUnit - Introduction to JUnit 5

  • View
    289

  • Download
    2

Embed Size (px)

Text of Renaissance of JUnit - Introduction to JUnit 5

PowerPoint Presentation

Renaissance of JUnit - Introduction to JUnit 5Jimmy Lujilu@digitalriver.comDigital River, Inc.

AgendaWhy JUnit 5JUnit LambdaJUnit 5 architectureJUnit the toolJUnit the platformDemoSummary

Why Rewrite JUnitJUnit 4.0 was released a decade agoAt the time of Java 5 and now java 9 is almost outModularity - big ball of mudTest discovery and execution - tightly coupledExtensibilityRunner APIEntire testing lifecycle heavyweightOnly one runner allowedRule APIlimited to executing some code before, during, and after a test was run

JUnit 4 modularitySingle junit.jar rules them all

JUnit LambdaCrowdfunding campaignThe code name of JUnit 5Main contributors:Johannes Link Marc PhilippMatthias MerdesStefan BechtoldSam Brannen

JUnit 5 OverviewJUnit Platform + JUnit Jupiter + JUnit VintageJUnit Platform: TestEngine and Launcher APIsJUnit Juptier: JUnit 5 TestEngine, new programming and extension modelJUnit Vintage: TestEngine for running JUnit 3 & 4ModularExtensibleModernBackward compatible

ArchitectureJUnit the toolAPIs for writing tests

JUnit the platformTestEngines for running testsLaunchers 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}

AssertionsassertEquals(), assertNotNull(), etc.assertThrows() and expectThrowsassertAll()Message is now the last parameterLambda syntax

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

AssumptionsAbort 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 FilteringBe used to filter test discovery and execution@Tag("fast")@Tag("model")class TaggingDemo {

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

}

Nested TestsExpress logical test groupNo @BeforeAll and @AfterAll in nested testsclass OuterTest {

@Nested class InnerTest {

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

Dependency InjectionNo arguments are allowed in previous version of JUnit in test methodsParameterResolverTestInfoParameterResolverTestReporterParameterResolverApplied to test constructor, @Test, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll methodsLeveraged by extensions

Dependency InjectionTestInfoParameterResolver@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 InjectionTestReporterParameterResolver@Testvoid reportSingleValue(TestReporter testReporter) { testReporter.publishEntry("a key", "a value");}

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

testReporter.publishEntry(values);}

Dependency InjectionInject 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 MethodsEnables multiple inheritance in tests@Test, @TestFactory, @BeforeEach, @AfterEach

interface EqualsContract extends Testable {

T createNotEqualValue();

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

Dynamic TestsTest cases are generated at runtimeVia method annotated with @TestFactory

@TestFactoryStream 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 APIUsed by IDEs and build tools to launch the frameworkCentral API for discovering and executing tests via one or more enginesLauncherDiscoveryRequestSlectors and filtersReturn a TestPlanFeedback 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 APITest engine discovers and executes testsfor a particular programming model, E.g. Spock, ScalaTestAutomatic discovery via Javas 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 PointsContainerExecutionConditionTestExecutionConditionTestInstancePostProcessorParameterResolverTestExecutionExceptionHandlerBefore(All|Each|TestExecution)CallbackAfter(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@APIInternalDeprecatedExperimentalMaintainedStable@API(Experimental)public interface TestExecutionListener { // code body}

DemoAssertions.assertTrue(demo.isSuccessful())

SummaryJava 8 supportImproved testing APIImproved modularity - separate of concernEasier to maintain and extendBackward compatible Aimed to be the foundation of java testing ecosystem

ReferenceJUnit 5 user guideJUnit 5 - An Early Test Drive part1, part2JUnit 5 - from Lambda to Alpha and beyondJUnit 5 - Shaping the Future of Testing on the JVM