Skip to content

Commit

Permalink
Dynamic test factory supports Iterable and Iterator (#58)
Browse files Browse the repository at this point in the history
- Simplify DynamicTestsDemo
  • Loading branch information
jlink authored and marcphilipp committed May 23, 2016
1 parent 11850fc commit abcdd7d
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 35 deletions.
57 changes: 36 additions & 21 deletions documentation/src/test/java/example/DynamicTestsDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.junit.gen5.api.Assertions;
import org.junit.gen5.api.Dynamic;
Expand All @@ -29,28 +26,49 @@
@RunWith(JUnit5.class)
public class DynamicTestsDemo {

// @Dynamic
List<String> dynamicTestsWithWrongReturnType() {
List<String> tests = new ArrayList<>();
tests.add("Hallo");
return tests;
}

@Dynamic
Stream<DynamicTest> myDynamicTest() {
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.stream();
return tests;
}

@Dynamic
Stream<DynamicTest> generatedTestsFromIterator() {
Iterator<String> stringIterator = Arrays.asList("ATest", "BTest", "CTest").iterator();
Stream<String> targetStream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(stringIterator, Spliterator.ORDERED), false);
return targetStream.map(s -> new DynamicTest(s, () -> {
Stream<DynamicTest> dynamicTestsFromStream() {
String[] testNames = new String[] { "test1", "test2" };
return Arrays.stream(testNames).map(name -> new DynamicTest(name, () -> {
}));
}

@Dynamic
Stream<DynamicTest> generatedTestsFromGeneratorFunction() {
Iterator<Integer> generator = new Iterator<Integer>() {
Iterator<DynamicTest> dynamicTestStreamFromIterator() {
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.iterator();
}

@Dynamic
Iterable<DynamicTest> dynamicTestStreamFromIterable() {
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;
}

@Dynamic
Iterator<DynamicTest> generatedTestsFromGeneratorFunction() {
Iterator<DynamicTest> generator = new Iterator<DynamicTest>() {
int counter = 0;

@Override
Expand All @@ -59,13 +77,12 @@ public boolean hasNext() {
}

@Override
public Integer next() {
return counter++;
public DynamicTest next() {
int index = counter++;
return new DynamicTest("test" + index, () -> Assertions.assertTrue(index % 11 != 0));
}
};
Stream<Integer> targetStream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(generator, Spliterator.ORDERED), false);
return targetStream.map(index -> new DynamicTest("test" + index, () -> Assertions.assertTrue(index % 11 != 0)));
return generator;
}

@Dynamic
Expand All @@ -87,10 +104,8 @@ public Integer next() {
return last;
}
};
Stream<Integer> targetStream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(generator, Spliterator.ORDERED), false);
return targetStream.map(
index -> new DynamicTest("test" + index, () -> Assertions.assertFalse(index % AVERAGE == 0)));
return DynamicTest.streamFrom(generator, index -> "test" + index,
index -> Assertions.assertFalse(index % AVERAGE == 0));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@

import static org.junit.gen5.api.Assertions.assertAll;
import static org.junit.gen5.api.Assertions.assertEquals;
import static org.junit.gen5.api.Assertions.assertTrue;
import static org.junit.gen5.engine.discovery.ClassSelector.forClass;
import static org.junit.gen5.engine.discovery.MethodSelector.forMethod;
import static org.junit.gen5.launcher.main.TestDiscoveryRequestBuilder.request;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;

import org.junit.gen5.api.Assertions;
import org.junit.gen5.api.Dynamic;
import org.junit.gen5.api.DynamicTest;
import org.junit.gen5.api.Test;
Expand All @@ -34,19 +36,68 @@ class DynamicTestGenerationTests extends AbstractJUnit5TestEngineTests {
public void dynamicTestMethodsAreCorrectlyDiscoveredForClassSelector() {
TestDiscoveryRequest request = request().select(forClass(MyDynamicTestCase.class)).build();
TestDescriptor engineDescriptor = discoverTests(request);
assertEquals(3, engineDescriptor.allDescendants().size(), "# resolved test descriptors");
assertEquals(5, engineDescriptor.allDescendants().size(), "# resolved test descriptors");
}

@Test
public void dynamicTestMethodIsCorrectlyDiscoveredForMethodSelector() {
TestDiscoveryRequest request = request().select(forMethod(MyDynamicTestCase.class, "myDynamicTest")).build();
TestDiscoveryRequest request = request().select(forMethod(MyDynamicTestCase.class, "dynamicStream")).build();
TestDescriptor engineDescriptor = discoverTests(request);
assertEquals(2, engineDescriptor.allDescendants().size(), "# resolved test descriptors");
}

@Test
public void dynamicTestsAreExecutedFromStream() {
TestDiscoveryRequest request = request().select(forMethod(MyDynamicTestCase.class, "myDynamicTest")).build();
TestDiscoveryRequest request = request().select(forMethod(MyDynamicTestCase.class, "dynamicStream")).build();

ExecutionEventRecorder eventRecorder = executeTests(request);

//dynamic test methods are counted as both container and test
assertAll( //
() -> assertEquals(3L, eventRecorder.getContainerStartedCount(), "# container started"),
() -> assertEquals(2L, eventRecorder.getDynamicTestRegisteredCount(), "# dynamic registered"),
() -> assertEquals(3L, eventRecorder.getTestStartedCount(), "# tests started"),
() -> assertEquals(2L, eventRecorder.getTestSuccessfulCount(), "# tests succeeded"),
() -> assertEquals(1L, eventRecorder.getTestFailedCount(), "# tests failed"),
() -> assertEquals(3L, eventRecorder.getContainerFinishedCount(), "# container finished"));
}

@Test
public void dynamicTestsAreExecutedFromCollection() {
TestDiscoveryRequest request = request().select(
forMethod(MyDynamicTestCase.class, "dynamicCollection")).build();

ExecutionEventRecorder eventRecorder = executeTests(request);

//dynamic test methods are counted as both container and test
assertAll( //
() -> assertEquals(3L, eventRecorder.getContainerStartedCount(), "# container started"),
() -> assertEquals(2L, eventRecorder.getDynamicTestRegisteredCount(), "# dynamic registered"),
() -> assertEquals(3L, eventRecorder.getTestStartedCount(), "# tests started"),
() -> assertEquals(2L, eventRecorder.getTestSuccessfulCount(), "# tests succeeded"),
() -> assertEquals(1L, eventRecorder.getTestFailedCount(), "# tests failed"),
() -> assertEquals(3L, eventRecorder.getContainerFinishedCount(), "# container finished"));
}

@Test
public void dynamicTestsAreExecutedFromIterator() {
TestDiscoveryRequest request = request().select(forMethod(MyDynamicTestCase.class, "dynamicIterator")).build();

ExecutionEventRecorder eventRecorder = executeTests(request);

//dynamic test methods are counted as both container and test
assertAll( //
() -> assertEquals(3L, eventRecorder.getContainerStartedCount(), "# container started"),
() -> assertEquals(2L, eventRecorder.getDynamicTestRegisteredCount(), "# dynamic registered"),
() -> assertEquals(3L, eventRecorder.getTestStartedCount(), "# tests started"),
() -> assertEquals(2L, eventRecorder.getTestSuccessfulCount(), "# tests succeeded"),
() -> assertEquals(1L, eventRecorder.getTestFailedCount(), "# tests failed"),
() -> assertEquals(3L, eventRecorder.getContainerFinishedCount(), "# container finished"));
}

@Test
public void dynamicTestsAreExecutedFromIterable() {
TestDiscoveryRequest request = request().select(forMethod(MyDynamicTestCase.class, "dynamicIterable")).build();

ExecutionEventRecorder eventRecorder = executeTests(request);

Expand All @@ -63,18 +114,38 @@ public void dynamicTestsAreExecutedFromStream() {
private static class MyDynamicTestCase {

@Dynamic
Stream<DynamicTest> myDynamicTest() {
Stream<DynamicTest> dynamicStream() {
List<DynamicTest> tests = new ArrayList<>();

tests.add(new DynamicTest("succeedingTest", () -> Assertions.assertTrue(true, "succeeding")));
tests.add(new DynamicTest("failingTest", () -> Assertions.assertTrue(false, "failing")));
tests.add(new DynamicTest("succeedingTest", () -> assertTrue(true, "succeeding")));
tests.add(new DynamicTest("failingTest", () -> assertTrue(false, "failing")));

return tests.stream();
}

@Dynamic
Stream<DynamicTest> otherDynamicTest() {
return new ArrayList<DynamicTest>().stream();
Collection<DynamicTest> dynamicCollection() {
List<DynamicTest> tests = new ArrayList<>();

tests.add(new DynamicTest("succeedingTest", () -> assertTrue(true, "succeeding")));
tests.add(new DynamicTest("failingTest", () -> assertTrue(false, "failing")));

return tests;
}

@Dynamic
Iterator<DynamicTest> dynamicIterator() {
List<DynamicTest> tests = new ArrayList<>();

tests.add(new DynamicTest("succeedingTest", () -> assertTrue(true, "succeeding")));
tests.add(new DynamicTest("failingTest", () -> assertTrue(false, "failing")));

return tests.iterator();
}

@Dynamic
Iterable<DynamicTest> dynamicIterable() {
return this::dynamicIterator;
}

}
Expand Down
20 changes: 20 additions & 0 deletions junit5-api/src/main/java/org/junit/gen5/api/DynamicTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@

package org.junit.gen5.api;

import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class DynamicTest {

private final String name;
Expand All @@ -27,4 +35,16 @@ public String getName() {
public Executable getExecutable() {
return executable;
}

public static <T extends Object> Stream<DynamicTest> streamFrom(Iterator<T> generator,
Function<T, String> nameSupplier, Consumer<T> assertion) {
Stream<T> targetStream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(generator, Spliterator.ORDERED), false);
return targetStream.map(element -> {
String testName = nameSupplier.apply(element);
Executable testExecutable = () -> assertion.accept(element);
return new DynamicTest(testName, testExecutable);
});

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@
import static org.junit.gen5.engine.junit5.execution.MethodInvocationContextFactory.methodInvocationContext;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.junit.gen5.api.DynamicTest;
import org.junit.gen5.api.extension.MethodInvocationContext;
import org.junit.gen5.api.extension.TestExtensionContext;
import org.junit.gen5.commons.JUnitException;
import org.junit.gen5.commons.meta.API;
import org.junit.gen5.engine.EngineExecutionListener;
import org.junit.gen5.engine.TestExecutionResult;
Expand Down Expand Up @@ -57,16 +63,47 @@ protected void invokeTestMethod(JUnit5EngineExecutionContext context, TestExtens
MethodInvocationContext methodInvocationContext = methodInvocationContext(
testExtensionContext.getTestInstance(), testExtensionContext.getTestMethod());

//Todo: Handle cast exceptions
Stream<DynamicTest> dynamicTestStream = (Stream<DynamicTest>) new MethodInvoker(testExtensionContext,
context.getExtensionRegistry()).invoke(methodInvocationContext);
MethodInvoker methodInvoker = new MethodInvoker(testExtensionContext, context.getExtensionRegistry());
Object dynamicMethodResult = methodInvoker.invoke(methodInvocationContext);
Stream<? extends DynamicTest> dynamicTestStream = toDynamicTestStream(dynamicMethodResult);

AtomicInteger index = new AtomicInteger();
dynamicTestStream.forEach(
dynamicTest -> registerAndExecute(dynamicTest, index.incrementAndGet(), listener));
try {
dynamicTestStream.forEach(
dynamicTest -> registerAndExecute(dynamicTest, index.incrementAndGet(), listener));
}
catch (ClassCastException cce) {
throw new JUnitException(
"Dynamic test must return Stream, Collection or Iterator of " + DynamicTest.class);
}

});
}

@SuppressWarnings("unchecked")
private Stream<? extends DynamicTest> toDynamicTestStream(Object dynamicMethodResult) {

if (dynamicMethodResult instanceof Stream) {
return (Stream<? extends DynamicTest>) dynamicMethodResult;
}
// use Collection's stream() implementation even though it implements Iterable
if (dynamicMethodResult instanceof Collection) {
Collection<? extends DynamicTest> dynamicTestCollection = (Collection<? extends DynamicTest>) dynamicMethodResult;
return dynamicTestCollection.stream();
}
if (dynamicMethodResult instanceof Iterable) {
Iterable<? extends DynamicTest> dynamicTestIterable = (Iterable<? extends DynamicTest>) dynamicMethodResult;
return StreamSupport.stream(dynamicTestIterable.spliterator(), false);
}
if (dynamicMethodResult instanceof Iterator) {
Iterator<? extends DynamicTest> dynamicTestIterator = (Iterator<? extends DynamicTest>) dynamicMethodResult;
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(dynamicTestIterator, Spliterator.ORDERED),
false);
}

throw new JUnitException("Dynamic test must return Stream, Iterable, or Iterator of " + DynamicTest.class);
}

private void registerAndExecute(DynamicTest dynamicTest, int index, EngineExecutionListener listener) {
UniqueId uniqueId = getUniqueId().append("dynamic-test", "%" + index);
DynamicTestTestDescriptor dynamicTestTestDescriptor = new DynamicTestTestDescriptor(uniqueId, dynamicTest,
Expand Down

0 comments on commit abcdd7d

Please sign in to comment.