Multi parameter tests - sageserpent-open/americium GitHub Wiki

Supplying independently varying test cases to a trial

Consider this wondrous artefact:

class PoorQualitySetMembershipPredicate<Element extends Comparable<Element>> implements Predicate<Element> {
    private final Comparable[] elements;

    public PoorQualitySetMembershipPredicate(Collection<Element> elements) {
        this.elements = elements.toArray(Comparable[]::new);
    }

    @Override
    public boolean test(Element element) {
        return 0 <= Arrays.binarySearch(elements, element);
    }
}

Let's test it:

final Trials<ImmutableList<Long>> lists =
        Trials.api().longs().immutableLists();

final Trials<Long> longs = Trials.api().longs();

lists
        .and(longs)
        .and(lists)
        .withLimit(10)
        .supplyTo((leftHandList, additionalLongToSearchFor,
                   rightHandList) -> {
            final Predicate<Long> systemUnderTest =
                    new PoorQualitySetMembershipPredicate(ImmutableList
                                                                  .builder()
                                                                  .addAll(leftHandList)
                                                                  .add(additionalLongToSearchFor)
                                                                  .addAll(rightHandList)
                                                                  .build());

            assertThat(systemUnderTest.test(additionalLongToSearchFor),
                       is(true));
        });

What we're doing here is to supply our test with a test case that is divided into three independently varying inputs - we have a left hand list of integers, a long value that we expect to find and a right hand list of integers. We surround the value we want to find by the two lists, and as these lists vary, so will the system under test - sometimes one or both of these will be empty, so we cover the cases where we are searching for a leading, trailing or singleton element too.

To supply the three inputs, we take three trials instances and gang them together using the .and combinator method - this builds a specialised form of a trials object that has custom overloads of .supplyTo for tests that either take the corresponding number of arguments, or a single tuple that has the arguments as components.

You may have spotted that lists occurs in two places in the gang - so does this mean that the parameters leftHandList and rightHandList will be the same for every trial? No - Americium does not treat trials instances as canned sequences of values, rather as specifications for what kind of values are produced. So when a trial is run, Americium is free to vary the first and third inputs independently of each other: they will produce the same kind of data, namely lists of long values, but in general those values will not coincide in a given trial - although they might every now and then.

Currently up to four elementary trials can be ganged together via .and.

What happens when we run the test?

java.lang.AssertionError: 
Expected: is <true>
     but: was <false>
Case:
[[1],0,[]]

Ah yes - we didn't sort the contents of the array PoorQualitySetMembershipPredicate.elements in the constructor, so the subsequent binary search will only work if the elements are already sorted. Let's fix it:

class AwesomeSetMembershipPredicate<Element extends Comparable<Element>> implements Predicate<Element> {
    private final Comparable[] elements;

    public AwesomeSetMembershipPredicate(Collection<Element> elements) {
        this.elements = elements.toArray(Comparable[]::new);

        Arrays.sort(this.elements, Comparator.naturalOrder());
    }

    @Override
    public boolean test(Element element) {
        return 0 <= Arrays.binarySearch(elements, element);
    }
}

That passes the test nicely.


Next topic: Reproducing a failing test case quickly...

⚠️ **GitHub.com Fallback** ⚠️