Reputation: 93
I'm implementing families of algorithms in Java and want to run the same set of algorithm family tests on each algorithm implementation. Let's look at the sorting algorithm case.
I have a few implementations of sorting algorithms. I want to run the same suite of parameterized tests on each implementation without copy/pasting the tests for each interface implementation.
An answer I've seen a few times to "how do I run the same set of tests on multiple class implementations" is to use a parameterized test with a list of the implementation classes to be tested as the input. However, these examples use the class as the only parameter. I want to run parameterized tests on the class under test.
The online examples of parameterized tests I've encountered use simple parameters (a single object/primitive or list of objects/primitives). In my case, I would want to provide the class to be tested and an array of values. I think this is possible, but it feels ugly and I would have to repeat the same test cases for each class type like so (not actual java syntax):
BubbleSorter.class, [1,2,3]
BubbleSorter.class, [3,2,1]
BubbleSorter.class, [-1,2,0]
MergeSorter.class, [1,2,3]
MergeSorter.class, [3,2,1]
MergeSorter.class, [-1,2,0]
InsertionSorter.class, [1,2,3]
...
Ideally, there would be some way to set the sort implementation once and let all of the parameterized tests worry only about the lists of values to be sorted and not which sort class to use.
My gut feeling says to use a parent abstract SorterTest containing the parameterized tests and use a factory method to allow each subclass (ex: MergeSorterTest) to decide which sorter implementation is used. However, a quick google about this seems to imply using inheritance to re-use tests cases in test code is frowned upon.
What is the recommended method for this situation? Is inheritance in test code allowable in some circumstances like this, or is there always a better alternative?
Also I'm using JUnit 5.
Upvotes: 2
Views: 417
Reputation: 9333
In my case, I would want to provide the class to be tested and an array of values.
You can combine multiple sources within e.g. a @MethodSource
. Let's assume you have something like a common Sorter
interface:
class SorterTest {
@ParameterizedTest
@MethodSource("args")
void test(Sorter sorter, List<Integer> list) {
// ...
}
static Stream<Arguments> args() {
// Combines each sorter implementation with each list to be sorted.
return sorters().flatMap(sorter -> lists().map(list -> Arguments.of(sorter, list)));
}
static Stream<Sorter> sorters() {
return Stream.of(new BubbleSorter(), new MergeSorter(), new InsertionSorter());
}
static Stream<List<Integer>> lists() {
return Stream.of(List.of(1, 2, 3), List.of(3, 2, 1), List.of(-1, 2, 0));
}
}
If you don't want to use a test oracle that provides the expected results (i.e. the sorted lists via a reference implementation), you can also combine three streams: sorters()
, unsorted()
, sorted()
. Moreover, you can use Supplier
s if you want to create your classes under test lazily:
static Stream<Supplier<Sorter>> sorters() {
return Stream.of(BubbleSorter::new, MergeSorter::new, InsertionSorter::new);
}
Upvotes: 0