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