On Github schauder / junit-lambda-talk
Jens Schauderhttp://blog.schauderhaft.de@jensschauderT-Systems on site services GmbH
Entwickler, Freunde dürfen mich Architekt nennen
JUnit Nutzer seit 2001
JUnit 5 Supporter
Entwickler von Degraph
Diskussion auf den XP-Days mit den JUnit Entwicklern
Indigogo Kampagne
Ziel: 25 000Euro
Ergebnis: 53 937Euro von 474 Unterstützern
Prototype
JUnit 5.0 Alpha releast
zusammen mit org.opentest4j
junit-commons junit-console junit-engine-api junit-gradle junit-launcher junit4-engine junit4-runner junit5-api junit5-engine surefire-junit5
start the launcher
launcher finds available Engines
launcher asks each Engine to discover Tests
launcher asks each Engine execute its Tests
Es gibt eine JUnit 4 Engine die JUnit4 Tests ausführen kann
Keine IDE Integration
Anotieren mit @RunWith(JUnit5.class)
JUnit5 Runner startet den Launcher wie oben
Gradle Plugin
Maven Plugin
ConsoleRunner
org.junit.gen5.*
Viele Namen haben sich geändert
Das soll die Verwirrung minimieren. Ob's klappt?
public class $01JUnit4Test {
@Test
public void testShouldFail(){
Assert.fail("JUnit4 test executed,
which is great, so this fails
... whatever");
}
}
Die Klassennamen haben nichts zu bedeuten
import org.junit.gen5.api.Assertions;
import org.junit.gen5.api.Test;
public class $02JUnit5Test {
@Test
public void testShouldFail() {
Assertions.fail("JUnit5 test executed,
which is great, so this fails
... whatever");
}
}
import org.junit.gen5.api.Assertions;
import org.junit.gen5.api.Test;
import org.junit.gen5.junit4.runner.JUnit5;
import org.junit.runner.RunWith;
@RunWith(JUnit5.class)
public class $03JUnit5AsJUnit4Test {
@Test
public void testShouldFail(){
Assertions.fail("This is a JUnit 5 Test which uses a Runner,
so it can run as a JUnit 5 or as a JUnit 4 Test,
which is great, so this fails
... whatever");
}
}
import org.junit.gen5.api.Assertions;
import org.junit.gen5.api.Assumptions;
import org.junit.gen5.api.Disabled;
import org.junit.gen5.api.DisplayName;
import org.junit.gen5.api.Test;
class $04ProperJUnit5Test {
// STUFF GOES HERE
}
Package Scope reicht
@Test
void shouldFail() { // again only package scope -> reduced noise
// Assertions replaces Assert for simple stuff
Assertions.fail("JUnit5 test executed, which is great,
so this fails ... whatever");
// you probably want to use AssertJ, Hamcrest or similar anyway
}
@Test
@Disabled // replaces @Ignored
void ignored() {
// does not get executed
}
@Test
void abortTests() {
// abort the test -> not failed, but execution stops anyway.
Assumptions.assumeTrue(false);
}
@Test
@DisplayName("Realy Awesome Name \uD83D\uDC4C")
void stupidName() {}
@Test
void testExceptions() {
final String argument = "forty-two";
final IllegalArgumentException exception = Assertions.expectThrows(
IllegalArgumentException.class, () -> {
parseRomanNumeral(argument);
});
Assertions.assertTrue(exception.getMessage().contains(argument));
}
int parseRomanNumeral(String numberString) {
if (numberString.equals("XXIII")) return 23;
throw new IllegalArgumentException("Can't parse " + numberString);
}
@ExtendWith({RandomParameterResolver.class})
public class $05MethodParameterResolver {
@Test
void testMethodParameterResolver(
String arg,
TestInfo testInfo // is provided by the TestInfoResolver,
// which is always present
) {
assertThat(testInfo.getDisplayName() , endsWith("Resolver"));
assertThat(arg, startsWith("Jens"));
}
}
public class RandomParameterResolver implements MethodParameterResolver{
@Override
public boolean supports(
Parameter parameter,
MethodInvocationContext methodInvocationContext,
ExtensionContext extensionContext
) throws ParameterResolutionException {
return parameter.getType() == String.class;
}
@Override
public Object resolve(
Parameter parameter,
MethodInvocationContext methodInvocationContext,
ExtensionContext extensionContext
) throws ParameterResolutionException {
return "Jens" + UUID.randomUUID();
}
}
Condition: disablen von TestsInstancePostProcessor: Testinstanzen manipulierenMethodParameterResolver: Parameter injizierenBeforeEachExtensionPointAfterEachExtensionPointBeforeAllExtensionPointAfterAllExtensionPoint
@ExtendWith({WithDatasource.class})
public class BeforeAfterExtensionTest implements NeedDatasource{
@Test
public void testSomething(){
System.out.println("in test one");
}
@Test
public void testSomethingElse(){
System.out.println("in test two");
}
@Override
public void set(MyDatasource ds) {
System.out.println("set called with " + ds);
}
}
public class WithDatasource implements
BeforeEachExtensionPoint, AfterEachExtensionPoint {
@Override
public void beforeEach(TestExtensionContext testExtensionContext) {
System.out.println("before");
((NeedDatasource)testExtensionContext.getTestInstance())
.set(new MyDatasource());
}
@Override
public void afterEach(TestExtensionContext testExtensionContext) {
System.out.println("after");
((NeedDatasource)testExtensionContext.getTestInstance())
.set(null);
}
}
public interface NeedDatasource {
void set(MyDatasource ds);
}
before set called with de.schauderhaft.junit.lambda.example.MyDatasource@4501b7af in test two after set called with null before set called with de.schauderhaft.junit.lambda.example.MyDatasource@6093dd95 in test one after set called with null
Keine Statement Abstraktion, wie bei Rules
=> Keine Threads, Mehrfachausführung ...
Abhängig von der Engine
@Dynamic
List<DynamicTest> dynamicTestsFromList() {
List<DynamicTest> tests = new ArrayList<>();
tests.add(new DynamicTest(
"succeedingTest",
() -> Assertions.assertTrue(true, "succeeding")
));
tests.add(new DynamicTest(
"failingTest",
() -> Assertions.assertTrue(false, "failing")
));
return tests;
}
public abstract class ClosureTestBase {
private List<DynamicTest> tests = new ArrayList<>();
protected void test(String name, Executable test){
tests.add(new DynamicTest(name, test));
}
@Dynamic
public List<DynamicTest> registeredTests(){
return tests;
}
}
class MyClosureTest extends ClosureTestBase{{
test("ein Test ein Test", () -> {Assertions.assertTrue(true);});
}}
for (Fixture f : asList(
f(1, 2, 3), // creates a Fixture instance
f(20, 30, 50),
f(-20, 12, -8))
)
test(format("%d plus %d is %d", f.summand1, f.summand2, f.sum),
() -> assertEquals(f.sum, f.summand1 + f.summand2)
);
testWithDependencies(
"those properties must contain some magic",
(Properties p) -> {
System.out.println("in test");
assertTrue(p.containsKey("magic.number"));
}
);
private void testWithDependencies(
String name,
Consumer<Properties> testNeedingProperties
) {
Properties p = new Properties();
p.put("magic.number", 42);
super.test(name, () -> testNeedingProperties.accept(p));
}
testWithSetupTearDown(
"test with setup/teardown",
() -> assertTrue(true)
);
private void testWithSetupTearDown(String name, Executable test) {
test(
name,
() -> {
try {
System.out.println("setup");
test.execute();
} finally {
System.out.println("teardown");
}
});
}
class ListTest {
@Nested
class WhenEmpty {
@Test
void canAdd() {
// ...
}
}
}
@Tag("super")
@Tags({@Tag("awesome"), @Tag("cool")})
class $08TaggedTest {
@Test
void impressiveTest() {
//
}
@Test
@Tag("nice")
void evenBetterTest() {
//
}
}
class $08TaggedTest {
@FastTest
void metaAnnotatedTest() {
//
}
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {}
@API(Experimental)
Mit den Ausprägungen
Internal, Deprecated, Experimental, Maintained, Stable
Das gleiche gilt für fast alles andere
d.h. man kann jetzt sehr direkt Einfluss nehmen
class SomeSpec extends ClosureSpec{{
test("ein Test", () -> {
// Testcontent should go here
});
}}
Halte ich für extrem wichtig!
Wenn ihr das auch so seht, äußert euch auf Github
Führt eure existierenden Tests mit JUnit 5 aus
Schreibt neue Tests mit JUnit 5
Schreibt eure Rules/Runner mit JUnit 5 Mitteln
Meckert freundlich, frühzeitig und konstruktiv auf Github
Fragen?
Jens Schauderhttp://blog.schauderhaft.de@jensschauderT-Systems on site services GmbH