Dynamic tests

As we know, in JUnit 3, we identified tests by parsing method names and checking whether they started with the word test. Then, in JUnit 4, we identified tests by collecting methods annotated with @Test. Both of these techniques share the same approach: tests are defined at compile time. This concept is what we call static testing.

Static tests are considered a limited approach, especially for the common scenario in which the same test is supposed to be executed for a variety of input data. In JUnit 4, this limitation was addressed in several ways. A very simple solution to the problem is to loop the input test data and exercising the same test logic (JUnit 4 example here). Following this approach, one test is executed until the first assertion fails:

package io.github.bonigarcia;

import org.junit.Test;

public class MyTest {

@Test
public void test() {
String[] input = { "A", "B", "C" };
for (String s : input) {
exercise(s);
}
}

private void exercise(String s) {
System.out.println(s);
}

}

A more elaborate solution is to use the JUnit 4 support for parameterized tests, using the parameterized runner. This approach does not create tests at runtime either, it simply repeats the same test several times depending on the parameters:

package io.github.bonigarcia;

import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class ParameterizedTest {

@Parameter(0)
public Integer input1;

@Parameter(1)
public String input2;

@Parameters(name = "My test #{index} -- input data: {0} and {1}")
public static Collection<Object[]> data() {
return Arrays
.asList(new Object[][] { { 1, "hello" }, { 2, "goodbye" } });
}

@Test
public void test() {
System.out.println(input1 + " " + input2);
}
}

We can see the execution of the preceding example in the Eclipse IDE:

Execution of JUnit 4’s parameterized test in Eclipse

On the other hand, JUnit 5 allows to generate test at runtime by a factory method that is annotated with @TestFactory. In contrast to @Test, a @TestFactory method is not a test but a factory. A @TestFactory method must return a Stream, Collection, Iterable, or Iterator of DynamicTest instances. These DynamicTest instances are executed lazily, enabling dynamic generation of test cases.

In order to create a dynamic test, we can use the static method dynamicTest of the class DynamicTest located in the org.junit.jupiter.api package. If we inspect the source code of this class, we can see that a DynamicTest is composed of a display name in form of the String and one executable object, which can be provided as lambda expressions or as method references.

Let's see several examples of dynamic tests. In the following example, the first dynamic test will fail, due to the fact we are not returning the expected collection of DynamicTests. The next three methods are very simple examples that demonstrate the generation of Collection, Iterable, and Iterator of DynamicTest instances:

package io.github.bonigarcia;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

class CollectionTest {

// Warning: this test will raise an exception
@TestFactory
List<String> dynamicTestsWithInvalidReturnType() {
return Arrays.asList("Hello");
}

@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(
dynamicTest("1st dynamic test", () ->
assertTrue(true)),
dynamicTest("2nd dynamic test", () -> assertEquals(4, 2
* 2)));
}

@TestFactory
Iterable<DynamicTest> dynamicTestsFromIterable() {
return Arrays.asList(
dynamicTest("3rd dynamic test", () ->
assertTrue(true)),
dynamicTest("4th dynamic test", () -> assertEquals(4, 2
* 2)));
}

@TestFactory
Iterator<DynamicTest> dynamicTestsFromIterator() {
return Arrays.asList(
dynamicTest("5th dynamic test", () ->
assertTrue(true)),
dynamicTest("6th dynamic test", () -> assertEquals(4, 2
* 2))).iterator();
}

}

These examples do not really exhibit dynamic behavior, but merely demonstrate the supported return types. Note that the first test is going to fail due to JUnitException:

Console output of the first example for dynamic test execution

The following example demonstrates how easy it is to generate dynamic tests for a given set of input data:

package io.github.bonigarcia;

import static org.junit.jupiter.api.DynamicTest.dynamicTest;

import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

class DynamicExampleTest {

@TestFactory
Stream<DynamicTest> dynamicTestsFromStream() {
Stream<String> inputStream = Stream.of("A", "B", "C");
return inputStream.map(
input -> dynamicTest("Display name for input " + input,
() -> {
System.out.println("Testing " + input);
}));
}

}

Notice that, in the end, three tests were executed, and these three tests were created at runtime by JUnit 5:

Console output of the second example for dynamic test execution

There is another possibility to create dynamic tests in JUnit 5, using the static method stream of the class DynamicTest. This method needs an input generator, a function that generates a display name based on an input value, and a test executor.

Let's see another example. We create a test factory, providing the input data as an Iterator, a display name function using a lambda expression, and finally, a test executor implemented with another lambda expression. In this example, the test executor basically asserts whether or not the input integer is even or odd:

package io.github.bonigarcia;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.stream;

import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer;

class StreamExampleTest {

@TestFactory
Stream<DynamicTest> streamTest() {
// Input data
Integer array[] = { 1, 2, 3 };
Iterator<Integer> inputGenerator = Arrays.asList(array).iterator();

// Display names
Function<Integer, String> displayNameGenerator = (
input) -> "Data input:" + input;

// Test executor
ThrowingConsumer<Integer> testExecutor = (input) -> {
System.out.println(input);
assertTrue(input % 2 == 0);
};

// Returns a stream of dynamic tests
return stream(inputGenerator, displayNameGenerator,
testExecutor);
}

}

The test will fail for odd inputs. As we can see, two out of three tests will fail:

Console output of dynamic test execution (example three)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.186.46