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
  2. @jeanneboyarsky 2 Change this report to add a field? No

    problem! Wait. What is this supposed to do?
  3. @jeanneboyarsky About Me • Java Champion • Author • Developer

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

    •Java 11 •Java 17 next Book giveaway at end!
  5. @jeanneboyarsky After this presentation • Presentation: http://speakerdeck.com/ boyarsky • Samples

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

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

    Test*.java IT*.java *Test.java *IT.java *Tests.java *ITCase.java *TestCase.java
  10. @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
  11. @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
  12. @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
  13. @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
  14. @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
  15. @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-
  16. @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
  17. @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?
  18. @jeanneboyarsky Assertions without Lambdas 24 assertTrue assertEquals assertIterableEquals assertFalse assertNotEquals

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

    String(“")) ; assertLinesMatch(List.of("xyz"), Arrays.asList("xyz")) ; All have optional last parameter with message
  20. @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
  21. @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
  22. @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
  23. @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.
  24. @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()) ; }
  25. @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
  26. @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>
  27. @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]>
  28. @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
  29. @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
  30. @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)
  31. @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
  32. @jeanneboyarsky Disabling Entirely 39 @Disable d @Tes t void uhOh()

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

    @Tes t @DisplayName("use case 1" ) void negativeNumber() { } }
  34. @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 >
  35. @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
  36. @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
  37. @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
  38. @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
  39. @jeanneboyarsky 48 JUnit 3.X JUnit 4.X JUnit 5.X Test names

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

    "Boeing"} ) void byValue(String company) { } Supports all primitives, strings, and classes
  43. @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) { }
  44. @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) { }
  45. @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")) ; } }
  46. @jeanneboyarsky Common Matchers 55 Type Examples Combiners anyOf, allOf, both,

    either Lists hasItem, is By type any Equality equalTo, nullValue, notNull, containsString, startsWith, endsWith
  47. @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) ; }
  48. @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) ; } }
  49. @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
  50. @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
  51. @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
  52. @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