Slide 1

Slide 1 text

@jeanneboyarsky 1 Intro to Testing with JUnit 5 Jeanne Boyarsky Sept 17, 2021 KCDC speakerdeck.com/boyarsky

Slide 2

Slide 2 text

@jeanneboyarsky 2 Change this report to add a field? No problem! Wait. What is this supposed to do?

Slide 3

Slide 3 text

@jeanneboyarsky 3 This is taking so long. Ok got it. My customers will be happy now.

Slide 4

Slide 4 text

@jeanneboyarsky 4 The numbers on the report are wrong. How could you let this happen!!!?

Slide 5

Slide 5 text

@jeanneboyarsky 5 Uh oh. I wish I had used JUnit!

Slide 6

Slide 6 text

@jeanneboyarsky • JUnit 5? • JUnit 4? • None 6

Slide 7

Slide 7 text

@jeanneboyarsky About Me • Java Champion • Author • Developer at NYC bank for 19+ years • FIRST Robotics Mentor 7

Slide 8

Slide 8 text

@jeanneboyarsky Pause for a Commercial 8 Java certs •Java 8 •Java 11 •Java 17 next Book giveaway at end!

Slide 9

Slide 9 text

@jeanneboyarsky Another Commercial 9

Slide 10

Slide 10 text

@jeanneboyarsky After this presentation • Presentation: http://speakerdeck.com/ boyarsky • Samples and code we write together: https://github.com/boyarsky/ 2021-kcdc-junit5 10

Slide 11

Slide 11 text

@jeanneboyarsky Agenda ⭐ Intro to JUnit 5 • Common testing patterns • Brief look back at history (JUnit 3 and 4) • JUnit 5 extensions • Interactive TDD live coding 11

Slide 12

Slide 12 text

@jeanneboyarsky • Released Sept 10, 2017 • Requires Java 8 • Today sprinkling in Java 9+ features 12

Slide 13

Slide 13 text

@jeanneboyarsky Basic Flow 13 class ReportTest { private Report report ; @BeforeEac h void setUp() { report = new Report() ; } @Tes t void createRow() { } } No need to make public Runs before each test Can have 1+ tests

Slide 14

Slide 14 text

@jeanneboyarsky Common Class name patterns 14 Unit Tests Integration Tests Test*.java IT*.java *Test.java *IT.java *Tests.java *ITCase.java *TestCase.java

Slide 15

Slide 15 text

@jeanneboyarsky Common test name examples 15 Method names testCreateRow() createRow() createRow_forNullParams() shouldHandleNullParams()

Slide 16

Slide 16 text

@jeanneboyarsky Assert Equals 16 @Tes t void createRow() { String expected = "Jeanne Boyarsky,Intro to JUnit 5" ; String actual = report.createRow ( "Jeanne Boyarsky", "Intro to JUnit 5") ; assertEquals(expected, actual, "row") ; } Optional message Order matters

Slide 17

Slide 17 text

@jeanneboyarsky Message on failing assert 17 row ==> expected: but was: Expected :Jeanne Boyarsk y Actual :Jeanne Boyarsky,Intro to JUnit 5 Remember that order matters! Message

Slide 18

Slide 18 text

@jeanneboyarsky BeforeAll 18 class ReportWithCacheTest { private static Map CACHE ; private ReportWithCache report ; @BeforeAl l static void createCache() { CACHE = Map.of("Intro to JUnit 5” , "Jeanne Boyarsky") ; } @BeforeEac h void setUp() { report = new ReportWithCache(CACHE) ; } Runs once static

Slide 19

Slide 19 text

@jeanneboyarsky BeforeAll 19 @Tes t void createRow_match() { String expected = "Jeanne Boyarsky,Intro to JUnit 5" ; String actual = report.createRow ( "Intro to JUnit 5") ; assertEquals(expected, actual, "row") ; } @Tes t void createRow_noMatch() { String expected = "Unknown,Lunch" ; String actual = report.createRow("Lunch") ; assertEquals(expected, actual, "default value") ; } @BeforeEach runs twice @BeforeAll runs once

Slide 20

Slide 20 text

@jeanneboyarsky Review: fill in the blanks 20 class BlanksTest { private String z ; @_________ _ static void a() { // lookup next i d } @_________ _ static void b() { Target.cache.clear(); } @_________ _ void c() { z = “test data"; } @_________ _ void d() { z = null; } @___ _ void e() { assertNotNull(z); } } BeforeAll AfterAll BeforeEach AfterEach Test

Slide 21

Slide 21 text

@jeanneboyarsky Review: what is output? 21 class FlowTest { private static String flow = ""; @BeforeAl l static void before() { flow+="x"; } @AfterAl l static void after() { System.out.println(flow); } @BeforeEac h void setUp() { flow+="y"; } @AfterEac h void tearDown() { flow+="-"; } @Tes t void one() { flow+="z"; } @Tes t void two() { flow+="z"; } } xyz-yz-

Slide 22

Slide 22 text

@jeanneboyarsky How about now? 22 class FlowTest { private static String flow = ""; @BeforeAl l static void before() { flow+="x"; } @AfterAl l static void after() { System.out.println(flow); } @BeforeEac h void setUp() { flow+="y"; } @AfterEac h void tearDown() { flow+="-"; } @Tes t void one() { flow+="1"; } @Tes t void two() { flow+="2"; } } “deterministic but non-obvious” order do not rely on it

Slide 23

Slide 23 text

@jeanneboyarsky If you really need order 23 @TestMethodOrder(MethodOrderer.OrderAnnotation.class ) class OrderedFlowTest { private static String flow = "" ; @AfterAl l static void after(){ System.out.println(flow); } @Tes t @Order(1 ) void one() { flow+="1"; } @Tes t @Order(2 ) void two() { flow+="2"; } } 12 Are you *sure* you need order?

Slide 24

Slide 24 text

@jeanneboyarsky Assertions without Lambdas 24 assertTrue assertEquals assertIterableEquals assertFalse assertNotEquals assertLinesMatch assertNull assertArrayEquals assertSame assertNotNull assertNotSame

Slide 25

Slide 25 text

@jeanneboyarsky Assert Examples 25 assertNotNull(“") ; assertTrue(“".isEmpty()) ; assertNotSame("", new String(“")) ; assertLinesMatch(List.of("xyz"), Arrays.asList("xyz")) ; All have optional last parameter with message

Slide 26

Slide 26 text

@jeanneboyarsky Review: fill in the blanks 26 @Tes t void asserts() { String message = "sum" ; int sum = Adder.sum(1, 2, 3) ; ______________(sum == 6) ; _______________(_____, _______, __________) ; } assertEquals assertTrue 6 sum message

Slide 27

Slide 27 text

@jeanneboyarsky assertTrue vs assertEquals 27 org.opentest4j.AssertionFailedError: expected: but was: org.opentest4j.AssertionFailedError: sum ==> expected: <6> but was: <7> Use most specific assertion you can

Slide 28

Slide 28 text

@jeanneboyarsky Agenda ✅ Intro to JUnit 5 ⭐ Common testing patterns • Brief look back at history (JUnit 3 and 4) • JUnit 5 extensions • Interactive TDD live coding 28

Slide 29

Slide 29 text

@jeanneboyarsky assertThrows 29 @Tes t void throwsException() { assertThrows(IllegalArgumentException.class, () -> Exceptions.validate(null)) ; } org.opentest4j.AssertionFailedError: Expected java.lang.IllegalArgumentException to be thrown, but nothing was thrown.

Slide 30

Slide 30 text

@jeanneboyarsky Which Better? 30 @Tes t void throwsException() { assertThrows(IllegalArgumentException.class, () -> Exceptions.validate(null)) ; } @Tes t void message() { IllegalArgumentException actual = assertThrows(IllegalArgumentException.class, () -> Exceptions.validate(null)) ; assertEquals("str can't be null", actual.getMessage()) ; }

Slide 31

Slide 31 text

@jeanneboyarsky assertTimeout 31 @Tes t void tooLong() { assertTimeout(Duration.ofSeconds(2), this::sketchyCode) ; } private void sketchyCode() { try { Thread.sleep(5_000) ; } catch (InterruptedException e) { // ignor e } } org.opentest4j.AssertionFailedError: execution exceeded timeout of 2000 ms by 3005 ms

Slide 32

Slide 32 text

@jeanneboyarsky assertAll - groups 32 @Tes t void bean() { Bean bean = new Bean("J", "B") ; assertAll("name" , () -> assertEquals("Jeanne", bean.getFirstName(), "first") , () -> assertEquals("Boyarsky", bean.getLastName(), "last")) ; } DefaultMultiCauseException: name (2 failures) org.opentest4j.AssertionFailedError: first ==> expected: but was: org.opentest4j.AssertionFailedError: last ==> expected: but was:

Slide 33

Slide 33 text

@jeanneboyarsky Side note - records 33 public record Record (String firstName, String lastName) { } @Tes t void record() { Record record = new Record("J", "B") ; Record expected = new Record ( “Jeanne", "Boyarsky") ; assertEquals(expected, record, "name") ; } org.opentest4j.AssertionFailedError: name ==> expected: but was:

Slide 34

Slide 34 text

@jeanneboyarsky assertAll - dependencies 34 @Tes t void bean() { Bean bean = null ; assertAll("found name” , () -> { assertNotNull(bean) ; assertAll("name values" , () -> assertEquals("Jeanne", bean.getFirstName(), "first") , () -> assertEquals("Boyarsky", bean.getLastName(), "last")) ; }) ; } org.opentest4j.AssertionFailedError: expected: not org.opentest4j.MultipleFailuresError: found name (1 failure) org.opentest4j.AssertionFailedError: expected: not Doesn’t run inner assertAll semicolons because lambda blocks

Slide 35

Slide 35 text

@jeanneboyarsky Assuming 35 @Tes t void bean() { Bean bean = null ; assumeFalse(bean == null, "no name") ; assertEquals("Jeanne", bean.getFirstName(), "first") ; assertEquals("Boyarsky", bean.getLastName(), "last") ; } Reports test is ignored Remember - simplest approach is best

Slide 36

Slide 36 text

@jeanneboyarsky Assumptions 36 Annotation Parameters assumeTrue boolean or BooleanSupplier optional message assumeFalse boolean or BooleanSupplier optional message assumingThat boolean or BooleanSupplier Executable (runs the Executable only if the assumption is true)

Slide 37

Slide 37 text

@jeanneboyarsky Running only on Linux 37 @Tes t void packagedOnServer() { assumeTrue(System.getProperty(“os.name" ) .toLowerCase().contains("linux"), "skip if not linux") ; // assertions her e } @Tes t @EnabledOnOs(OS.LINUX ) void linuxAnnotation() { // assertions her e } Simplest approach is still best Also reports as ignored

Slide 38

Slide 38 text

@jeanneboyarsky OS Annotations 38 Annotations EnabledOnOS DisabledOnOS @EnabledOnOs(LINUX ) @EnabledOnOs({OS.LINUX, OS.MAC } ) OS WINDOWS AIX MAC SOLARIS LINUX OTHER

Slide 39

Slide 39 text

@jeanneboyarsky Disabling Entirely 39 @Disable d @Tes t void uhOh() { // assertions her e } Also reports as ignored

Slide 40

Slide 40 text

@jeanneboyarsky Display Names 40 @DisplayName("Requirement 123" ) class DisplayNameTest { @Tes t @DisplayName("use case 1" ) void negativeNumber() { } }

Slide 41

Slide 41 text

@jeanneboyarsky Tags & Build Tools 41 @Tag("slow" ) class TagTest { @Tes t @Tag("full-suite" ) void test() { } } test { useJUnitPlatform { excludeTags 'slow', 'abc ' } } maven-surefire-plugi n slow

Slide 42

Slide 42 text

@jeanneboyarsky Tags in the IDE 42

Slide 43

Slide 43 text

@jeanneboyarsky Repeated Test 43 @RepeatedTest(100 ) void threadsDoNotFail() { } Runs 100 times. Fails test if any fail

Slide 44

Slide 44 text

@jeanneboyarsky Testing System.out.println 44 class HelloWorldTest { private PrintStream originalSystemOut ; private ByteArrayOutputStream mockSystemOut ; @BeforeEac h void setUp() { mockSystemOut = new ByteArrayOutputStream() ; System.setOut(new PrintStream(mockSystemOut)) ; } @AfterEac h void restoreSystemOut() { System.setOut(originalSystemOut) ; } @Tes t void prints() { HelloWorld.main() ; String actual = mockSystemOut.toString() ; assertEquals("Hello World", actual.strip()) ; } } Warning: be careful if running multi-threaded

Slide 45

Slide 45 text

@jeanneboyarsky Agenda ✅ Intro to JUnit 5 ✅ Common testing patterns ⭐ Brief look back at history (JUnit 3 and 4) • JUnit 5 extensions • Interactive TDD live coding 45

Slide 46

Slide 46 text

@jeanneboyarsky 46 JUnit 3.X JUnit 4.X JUnit 5.X Released ? 2006 2017 Superclass TestCase No No Package junit .framework org.junit org.junit .jupiter…. Minimum Java version ? 5 8

Slide 47

Slide 47 text

@jeanneboyarsky 47 JUnit 3.X JUnit 4.X JUnit 5.X assertEquals [message,] expected, actual [message,] expected, actual expected, actual [,message] Setup method name setUp() any any Access control public public package private or higher

Slide 48

Slide 48 text

@jeanneboyarsky 48 JUnit 3.X JUnit 4.X JUnit 5.X Test names test* Any Any Skipping a test Comment it out @Ignore @Disabled

Slide 49

Slide 49 text

@jeanneboyarsky Agenda ✅ Intro to JUnit 5 ✅ Common testing patterns ✅ Brief look back at history (JUnit 3 and 4) ⭐ JUnit 5 extensions • Interactive TDD live coding 49

Slide 50

Slide 50 text

@jeanneboyarsky Parameterized Tests 50 Dependenc y testImplementatio n 'org.junit.jupiter:junit-jupiter-params:5.7.2 ' enum Env { DEV, QA, PROD} ; @ParameterizedTes t @EnumSource(Env.class ) void byEnum(Env env) { }

Slide 51

Slide 51 text

@jeanneboyarsky Parameterized Tests 51 @ParameterizedTes t @ValueSource(strings = { "SpaceX", "Boeing"} ) void byValue(String company) { } Supports all primitives, strings, and classes

Slide 52

Slide 52 text

@jeanneboyarsky Parameterized Tests 52 static Stream data() { return Stream.generate ( () -> Math.random()).limit(10) ; } @ParameterizedTes t @MethodSource("data" ) void byMethod(Double number) { } @ParameterizedTes t @MethodSource("com.jeanneboyarsky.kcdc.junit5." + "extensions.Enabled#getEnabled" ) void byMethodElsewhere(String name) { }

Slide 53

Slide 53 text

@jeanneboyarsky Retry Failed Tests 53 Dependenc y testImplementation ‘io.github.artsok:rerunner-jupiter:2.1.6 ' @RepeatedIfExceptionsTest(repeats = 3 ) void flakeyTest() { throw new RuntimeException("Uh oh") ; } @RepeatedIfExceptionsTest(repeats = 3 ) void goodTest() { } @ParameterizedRepeatedIfExceptionsTes t @ValueSource(ints = {1,2,3} ) void params(int num) { }

Slide 54

Slide 54 text

@jeanneboyarsky Hamcrest Matchers 54 Dependenc y testImplementation 'org.hamcrest:hamcrest-library:2.2 ' import org.junit.jupiter.api.Test ; import static org.hamcrest.CoreMatchers.containsString ; import static org.hamcrest.MatcherAssert.assertThat ; public class HamcrestTest { @Tes t void matcher() { assertThat("kc", containsString("kc")) ; } }

Slide 55

Slide 55 text

@jeanneboyarsky Common Matchers 55 Type Examples Combiners anyOf, allOf, both, either Lists hasItem, is By type any Equality equalTo, nullValue, notNull, containsString, startsWith, endsWith

Slide 56

Slide 56 text

@jeanneboyarsky Mockito 56 Dependenc y testImplementation 'org.mockito:mockito-junit-jupiter:3.12.1 ' testImplementation 'org.mockito:mockito-core:3.12.1 ' @ExtendWith(MockitoExtension.class ) public class MockingTest { private Processor processor ; @Moc k Function functionMock ; @BeforeEac h void setUp() { processor = new Processor(functionMock) ; }

Slide 57

Slide 57 text

@jeanneboyarsky Mockito 57 @Tes t void process() { String text = "abc" ; int expected = 42 ; when(functionMock.apply(text) ) .thenReturn(expected) ; int actual = processor.process(text) ; assertEquals(expected, actual, "result") ; verify(functionMock, times(1)).apply(text) ; } }

Slide 58

Slide 58 text

@jeanneboyarsky Common Verifiers 58 Examples atLeastOnce(), atLeast(int) atMostOnce(), atMost() times() never() Also useful: @Mock(lenient=true)

Slide 59

Slide 59 text

@jeanneboyarsky JUnit Pioneer 59 Dependenc y testImplementation 'org.junit-pioneer:junit-pioneer:1.4.2 ' • Retry on failure (no param version) • Threadsafe scheduler for System.in/out • And so much more

Slide 60

Slide 60 text

@jeanneboyarsky JUnit Pioneer 60 Dependenc y testImplementation 'org.junit-pioneer:junit-pioneer:1.4.2 ' • Retry on failure (no param version) • Threadsafe scheduler for System.in/out • And so much more

Slide 61

Slide 61 text

@jeanneboyarsky Converting from JUnit 4 61 • IntelliJ does this well • Before that, I wrote a program which was adopted: https://github.com/junit-pioneer/ convert-junit4-to-junit5

Slide 62

Slide 62 text

@jeanneboyarsky Agenda ✅ Intro to JUnit 5 ✅ Common testing patterns ✅ Brief look back at history (JUnit 3 and 4) ✅ JUnit 5 extensions ⭐ Interactive TDD live coding 62

Slide 63

Slide 63 text

@jeanneboyarsky Live TDD 63 • Let’s write some code! • Then book giveaway