Upgrade to Pro — share decks privately, control downloads, hide ads and more …

2021 KCDC JUnit 5

2021 KCDC JUnit 5

Intro to JUnit 5

3906b007e5c4150115e319a01d2f4ab8?s=128

Jeanne Boyarsky

September 17, 2021
Tweet

More Decks by Jeanne Boyarsky

Other Decks in Programming

Transcript

  1. @jeanneboyarsky 1 Intro to Testing with JUnit 5 Jeanne Boyarsky

    Sept 17, 2021 KCDC speakerdeck.com/boyarsky
  2. @jeanneboyarsky 2 Change this report to add a field? No

    problem! Wait. What is this supposed to do?
  3. @jeanneboyarsky 3 This is taking so long. Ok got it.

    My customers will be happy now.
  4. @jeanneboyarsky 4 The numbers on the report are wrong. How

    could you let this happen!!!?
  5. @jeanneboyarsky 5 Uh oh. I wish I had used JUnit!

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

  7. @jeanneboyarsky About Me • Java Champion • Author • Developer

    at NYC bank for 19+ years • FIRST Robotics Mentor 7
  8. @jeanneboyarsky Pause for a Commercial 8 Java certs •Java 8

    •Java 11 •Java 17 next Book giveaway at end!
  9. @jeanneboyarsky Another Commercial 9

  10. @jeanneboyarsky After this presentation • Presentation: http://speakerdeck.com/ boyarsky • Samples

    and code we write together: https://github.com/boyarsky/ 2021-kcdc-junit5 10
  11. @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
  12. @jeanneboyarsky • Released Sept 10, 2017 • Requires Java 8

    • Today sprinkling in Java 9+ features 12
  13. @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
  14. @jeanneboyarsky Common Class name patterns 14 Unit Tests Integration Tests

    Test*.java IT*.java *Test.java *IT.java *Tests.java *ITCase.java *TestCase.java
  15. @jeanneboyarsky Common test name examples 15 Method names testCreateRow() createRow()

    createRow_forNullParams() shouldHandleNullParams()
  16. @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
  17. @jeanneboyarsky Message on failing assert 17 row ==> expected: <Jeanne

    Boyarsky> but was: <Jeanne Boyarsky,Intro to JUnit 5 > Expected :Jeanne Boyarsk y Actual :Jeanne Boyarsky,Intro to JUnit 5 Remember that order matters! Message
  18. @jeanneboyarsky BeforeAll 18 class ReportWithCacheTest { private static Map<String, String>

    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
  19. @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
  20. @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
  21. @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-
  22. @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
  23. @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?
  24. @jeanneboyarsky Assertions without Lambdas 24 assertTrue assertEquals assertIterableEquals assertFalse assertNotEquals

    assertLinesMatch assertNull assertArrayEquals assertSame assertNotNull assertNotSame
  25. @jeanneboyarsky Assert Examples 25 assertNotNull(“") ; assertTrue(“".isEmpty()) ; assertNotSame("", new

    String(“")) ; assertLinesMatch(List.of("xyz"), Arrays.asList("xyz")) ; All have optional last parameter with message
  26. @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
  27. @jeanneboyarsky assertTrue vs assertEquals 27 org.opentest4j.AssertionFailedError: expected: <true> but was:

    <false> org.opentest4j.AssertionFailedError: sum ==> expected: <6> but was: <7> Use most specific assertion you can
  28. @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
  29. @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.
  30. @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()) ; }
  31. @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
  32. @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: <Jeanne> but was: <J> org.opentest4j.AssertionFailedError: last ==> expected: <Boyarsky> but was: <B>
  33. @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: <Record[firstName=Jeanne, lastName=Boyarsky]> but was: <Record[firstName=J, lastName=B]>
  34. @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 <null> org.opentest4j.MultipleFailuresError: found name (1 failure) org.opentest4j.AssertionFailedError: expected: not <null> Doesn’t run inner assertAll semicolons because lambda blocks
  35. @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
  36. @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)
  37. @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
  38. @jeanneboyarsky OS Annotations 38 Annotations EnabledOnOS DisabledOnOS @EnabledOnOs(LINUX ) @EnabledOnOs({OS.LINUX,

    OS.MAC } ) OS WINDOWS AIX MAC SOLARIS LINUX OTHER
  39. @jeanneboyarsky Disabling Entirely 39 @Disable d @Tes t void uhOh()

    { // assertions her e } Also reports as ignored
  40. @jeanneboyarsky Display Names 40 @DisplayName("Requirement 123" ) class DisplayNameTest {

    @Tes t @DisplayName("use case 1" ) void negativeNumber() { } }
  41. @jeanneboyarsky Tags & Build Tools 41 @Tag("slow" ) class TagTest

    { @Tes t @Tag("full-suite" ) void test() { } } test { useJUnitPlatform { excludeTags 'slow', 'abc ' } } <plugin > <artifactId > maven-surefire-plugi n </artifactId > <configuration > <groups>slow</groups > </configuration > </plugin >
  42. @jeanneboyarsky Tags in the IDE 42

  43. @jeanneboyarsky Repeated Test 43 @RepeatedTest(100 ) void threadsDoNotFail() { }

    Runs 100 times. Fails test if any fail
  44. @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
  45. @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
  46. @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
  47. @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
  48. @jeanneboyarsky 48 JUnit 3.X JUnit 4.X JUnit 5.X Test names

    test* Any Any Skipping a test Comment it out @Ignore @Disabled
  49. @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
  50. @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) { }
  51. @jeanneboyarsky Parameterized Tests 51 @ParameterizedTes t @ValueSource(strings = { "SpaceX",

    "Boeing"} ) void byValue(String company) { } Supports all primitives, strings, and classes
  52. @jeanneboyarsky Parameterized Tests 52 static Stream<Double> 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) { }
  53. @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) { }
  54. @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")) ; } }
  55. @jeanneboyarsky Common Matchers 55 Type Examples Combiners anyOf, allOf, both,

    either Lists hasItem, is By type any Equality equalTo, nullValue, notNull, containsString, startsWith, endsWith
  56. @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) ; }
  57. @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) ; } }
  58. @jeanneboyarsky Common Verifiers 58 Examples atLeastOnce(), atLeast(int) atMostOnce(), atMost() times()

    never() Also useful: @Mock(lenient=true)
  59. @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
  60. @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
  61. @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
  62. @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
  63. @jeanneboyarsky Live TDD 63 • Let’s write some code! •

    Then book giveaway