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

2021 KCDC JUnit 5

2021 KCDC JUnit 5

Intro to JUnit 5

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


    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. @jeanneboyarsky 5
    Uh oh.


    I wish I had used JUnit!

    View Slide

  6. @jeanneboyarsky
    • JUnit 5?


    • JUnit 4?


    • None


    6

    View Slide

  7. @jeanneboyarsky
    About Me
    • Java Champion


    • Author


    • Developer at NYC
    bank for 19+
    years


    • FIRST Robotics
    Mentor
    7

    View Slide

  8. @jeanneboyarsky
    Pause for a Commercial
    8
    Java certs


    •Java 8


    •Java 11


    •Java 17 next


    Book giveaway at end!

    View Slide

  9. @jeanneboyarsky
    Another Commercial
    9

    View Slide

  10. @jeanneboyarsky
    After this presentation
    • Presentation:


    http://speakerdeck.com/
    boyarsky


    • Samples and code we write
    together:


    https://github.com/boyarsky/
    2021-kcdc-junit5
    10

    View Slide

  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

    View Slide

  12. @jeanneboyarsky
    • Released Sept 10,
    2017


    • Requires Java 8


    • Today sprinkling in
    Java 9+ features
    12

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  17. @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

    View Slide

  18. @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

    View Slide

  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

    View Slide

  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

    View Slide

  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-

    View Slide

  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

    View Slide

  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?

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  27. @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

    View Slide

  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

    View Slide

  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.

    View Slide

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

    }

    View Slide

  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

    View Slide

  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: but was:


    org.opentest4j.AssertionFailedError: last ==>


    expected: but was:

    View Slide

  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:
    but was:

    View Slide

  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


    org.opentest4j.MultipleFailuresError: found name (1 failure)


    org.opentest4j.AssertionFailedError: expected: not
    Doesn’t run
    inner assertAll
    semicolons
    because
    lambda blocks

    View Slide

  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

    View Slide

  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)

    View Slide

  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

    View Slide

  38. @jeanneboyarsky
    OS Annotations
    38
    Annotations
    EnabledOnOS
    DisabledOnOS
    @EnabledOnOs(LINUX
    )

    @EnabledOnOs({OS.LINUX, OS.MAC }
    )

    OS
    WINDOWS AIX
    MAC SOLARIS
    LINUX OTHER

    View Slide

  39. @jeanneboyarsky
    Disabling Entirely
    39
    @Disable
    d

    @Tes
    t

    void uhOh()
    {

    // assertions her
    e

    }

    Also reports as ignored

    View Slide

  40. @jeanneboyarsky
    Display Names
    40
    @DisplayName("Requirement 123"
    )

    class DisplayNameTest
    {

    @Tes
    t

    @DisplayName("use case 1"
    )

    void negativeNumber()
    {

    }

    }

    View Slide

  41. @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>

    >

    >

    View Slide

  42. @jeanneboyarsky
    Tags in the IDE
    42

    View Slide

  43. @jeanneboyarsky
    Repeated Test
    43
    @RepeatedTest(100
    )

    void threadsDoNotFail()
    {

    }

    Runs 100 times.


    Fails test if any fail

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    }

    View Slide

  51. @jeanneboyarsky
    Parameterized Tests
    51
    @ParameterizedTes
    t

    @ValueSource(strings = { "SpaceX", "Boeing"}
    )

    void byValue(String company)
    {

    }

    Supports all primitives, strings, and classes

    View Slide

  52. @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)
    {

    }

    View Slide

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

    View Slide

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

    }

    }

    View Slide

  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

    View Slide

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

    }

    View Slide

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

    }

    }

    View Slide

  58. @jeanneboyarsky
    Common Verifiers
    58
    Examples
    atLeastOnce(), atLeast(int)
    atMostOnce(), atMost()
    times()
    never()
    Also useful:


    @Mock(lenient=true)

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  63. @jeanneboyarsky
    Live TDD
    63
    • Let’s write some code!


    • Then book giveaway

    View Slide