A presentation on JUnit Pioneer given at Fortitude Technologies on Mar. 4, 2021. JUnit Pioneer is an extension library for JUnit 5 (Jupiter).
Sample code on GitHub at:
https://github.com/sleberknight/junit-pioneering-presentation-code
JUnit Pioneer home page:
https://junit-pioneer.org
4. Simple Example
@ParameterizedTest
@RandomIntSource(min = 5, max = 10, count = 50)
void shouldDoSomethingWithRandomInts(int value) {
// test code...
}
Injecting method
parameters into
tests
Example from https://github.com/kiwiproject/kiwi-test/
12. Having Issues?
public class SystemOutIssueProcessor
implements IssueProcessor {
@Override
public void processTestResults(List<IssueTestSuite> suites) {
// do something with each test suite
}
}
@Test
@Issue("JUPI-1234")
void shouldFix1234() {
// test code for issue JUPI-1234
}
#1 Annotate
#2 Define processor
14. I need those TPS reports...
class ReportEntriesTest {
@Test
@ReportEntry("I'm Going To Need Those TPS Reports ASAP")
void testWithReportEntry() { ... }
@Test
@ReportEntry(key = "result", value = "success",
when = PublishCondition.ON_SUCCESS)
@ReportEntry(key = "result", value = "failed",
when = ReportEntry.PublishCondition.ON_FAILURE)
void testWithConditionalEntries() { ... }
}
#1 Annotate
15. public class SimpleTestExecutionListener
implements TestExecutionListener { ... }
#2 Create a JUnit
TestExecutionListener
com.fortitudetec.junit.pioneering.SimpleTestExecutionListener
META-INF/services/org.junit.platform.launcher.TestExecutionListener
#3 Register implementation
16. Disabling on display name
Selectively disable parameterized tests
Specify substrings and/or regex to match...
...and disable any tests with a matching name
17. @DisableIfDisplayName(contains = "42")
@ParameterizedTest(name = "Test scenario {0}")
@ValueSource(ints = {1, 24, 42, 84, 168, 420, 4200, 4242, 17640})
void shouldDisableUsingStringContains(int value) {
if (String.valueOf(value).contains("42")) {
fail("Should not have received %s", value);
}
}
Using a substring to specify
disabling condition
matching
against the
generated
test name
20. @DisableIfDisplayName(matches = ".*[0-9][3|5]")
@ParameterizedTest(name = "Product: FTCH-000-{0}")
@RandomIntSource(min = 0, max = 1_000, count = 500)
void shouldDisableWhenProductCodeEndsWith_X3_Or_X5(int code) {
var codeString = String.valueOf(code);
if (PRODUCT_NUMBER_PATTERN.matcher(codeString).matches()) {
fail("Should not have received %d", code);
}
}
Combine a regex
with randomized test input
@RandomIntSource from https://github.com/kiwiproject/kiwi-test/
21.
22. Do, or do not, there is no try
class RetryingAnnotationTest {
@RetryingTest(2)
void shouldFail() {
flakyObject.failsFirstTwoTimes();
}
@RetryingTest(3)
void shouldPass() {
flakyObject.failsFirstTwoTimes();
}
}
25. & Stdout
@Test
@StdIo
void shouldInterceptStandardOutput(StdOut stdOut) {
System.out.println("The answer is 24");
System.out.println("No, the real answer is always 42");
assertThat(stdOut.capturedLines()).containsExactly(
"The answer is 24",
"No, the real answer is always 42"
);
}
26. How long did that take?
class StopwatchTest {
@RepeatedTest(value = 10)
@Stopwatch
void shouldReportElapsedTime() { ... }
}
Elapsed time will be published
via a TestReporter, so that a
TestExecutionListener can
receive it
27. Out on the range...
Create ranges of ints, longs, etc. as test input
Use with parameterized tests
Specify lower and upper bounds
Upper bound can be inclusive or exclusive
28. @ParameterizedTest
@IntRangeSource(from = 0, to = 10)
void shouldGenerateIntegers(int value) {
assertThat(value).isBetween(0, 9);
failIfSeeUpperBound(value, 10);
}
'from' is inclusive;
'to' is exclusive by default
32. The best vintage?
Pioneer's vintage @Test replaces JUnit 4 @Test...
...but marks the method as a Jupiter test
Supports expected and timeout parameters
35. Generate cartesian product of all inputs
Use @CartesianProductTest instead of
Jupiter @Test annotations
Pioneer provides custom argument sources
You can also provide a custom @ArgumentsSource
39. Danger Will Robinson!
You must use Pioneer's @CartesianXyxSource
with @CartesianProductTest
Trying to use plain Jupiter sources like
@ValueSource will result in exceptions
40. @CartesianProductTest
@IntRangeSource(from = 1, to = 4, closed = true)
@LongRangeSource(from = 1000, to = 1005, closed = true)
void shouldWorkWithRangeSources(int x, long y) {
assertThat(Range.closed(1, 4).contains(x)).isTrue();
assertThat(Range.closed(1000L, 1005L).contains(y)).isTrue();
}
Also works with
Pioneer range
sources
41. Using several of Pioneer's
@XyzRangeSource annotations
with @CartesianProductTest
42. Can also create custom factory methods
Cartesian Argument Factories
Must be static & return CartesianProductTest.Sets
Use naming convention or factory parameter
in @CartesianProductTest
43. @CartesianProductTest(factory = "customProduct")
void shouldAllowCustomArgumentFactory(
String greek, Result result, int level) {
assertThat(equalsAny(greek, "Alpha", "Beta", "Gamma")).isTrue();
assertThat(result).isNotNull();
assertThat(Range.closedOpen(0, 5).contains(level)).isTrue();
}
static CartesianProductTest.Sets customProduct() {
return new CartesianProductTest.Sets()
.add("Alpha", "Beta", "Gamma")
.add((Object[]) Result.values())
.addAll(Stream.iterate(0, val -> val + 1).limit(5));
}
Instead, we could name the test
'customProduct' (but, I think using
factory is more understandable)
47. References
JUnit Pioneer website
https://junit-pioneer.org
On GitHub
https://github.com/junit-pioneer/junit-pioneer
IETF BCP 47
https://tools.ietf.org/html/bcp47
IETF language tag (wikipedia)
https://en.wikipedia.org/wiki/IETF_language_tag
JDK 11 Locale
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Locale.html
Local Helper website
https://lh.2xlibre.net
48. Photos & Images
An artist's impression of a Pioneer spacecraft on its way to interstellar space
https://commons.wikimedia.org/wiki/File:An_artist%27s_impression_of_a_Pioneer_spacecraft_on_its_way_to_interstellar_space.jpg
One more thing...
https://www.flickr.com/photos/mathoov/4681491052
https://creativecommons.org/licenses/by-nc-nd/2.0/