2. Why all the primitives?¶
Short answer: To provide bi-valued predicates for primitives for use in testing, in particular testing relative equality of nested arrays:
@Test
void testNestedArrays() {
DoubleDoubleBiPredicate equal = Predicates.doublesAreRelativelyClose(1e-3);
double[][] expected = {
{1, 2, 30},
{4, 5, 6},
};
double[][] actual = {
{1, 2, 30.01},
{4.0001, 5, 6},
};
TestAssertions.assertArrayTest(expected, actual, equal);
}
2.1. Java Predicates¶
Java supports testing of primitives from java.util.function
using:
@FunctionalInterface
public interface DoublePredicate {
/**
* Evaluates this predicate on the given argument.
*
* @param value the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(double value);
// Default methods
}
for the double
primitive type and a similar IntPredicate
and LongPredicate
for the
int
and long
primitive types.
The Java language can natively convert float
to double
and byte
, char
, and short
to int
. So the standard Java predicates can be used
to test all primitive types except boolean
. This can be supported with a simple cast to 1 or 0.
Thus Java predicates already support testing single-valued primitives.
In the case of bi-valued predicates Java only provides BiPredicate<T, U>
:
@FunctionalInterface
public interface BiPredicate<T, U> {
/**
* Evaluates this predicate on the given arguments.
*
* @param t the first input argument
* @param u the second input argument
* @return {@code true} if the input arguments match the predicate,
* otherwise {@code false}
*/
boolean test(T t, U u);
// Default methods
}
Primitives must be tested via the auto-boxing of primitives in their respective class.
2.2. A Predicate Library¶
The motivation for the library was to provide bi-valued predicates for primitives for use in testing. By providing predicates for all primitives it ensures:
- The test is explicit
- The test can assume a range for the value and natural behaviour
boolean
is supported
An example is that the maximum difference between int
values requires using long
to prevent overflow and
long
differences require using BigInteger
.
The predicate library provides the following generic interfaces:
@FunctionalInterface
public interface TypePredicate {
boolean test(type value);
// default methods
}
@FunctionalInterface
public interface TypeTypeBiPredicate {
boolean test(type value1, type value2);
// default methods
}
where <type>
is one of: boolean
, byte
, char
, double
, float
, int
,
long
and short
.
The predicates copy the functionality of Java’s java.util.function.Predicate
by supporting
default methods for negate
and the logical combination using or
and and
but also add xor
.
However in contrast to the Java version these default interface methods return concrete classes
and not lambda expressions. This is to support a feature useful for testing where classes
implementing the interface also implement Supplier<String>
to provide a description.
Thus logical combination predicates can provide a logical combination of the description of
their composed predicates.
Note that the library duplicates DoublePredicate
, IntPredicate
and LongPredicate
.
The GDSC Test versions do not extend their respective Java versions. This avoids a confusing API
where predicates do not function identically due to argument types to default methods
(or
and and
) either accepting java.util.function
predicates or GDSC Test predicates.
Since these are functional interfaces it is easy to convert between the two using a method
reference to the test
method:
final int answer = 42;
uk.ac.sussex.gdsc.test.api.function.IntPredicate isAnswer1 = v -> v == answer;
java.util.function.IntPredicate isAnswer2 = isAnswer1::test;
uk.ac.sussex.gdsc.test.api.function.IntPredicate isAnswer3 = isAnswer2::test;
This allows using the GDSC test predicates within the standard Java framework:
uk.ac.sussex.gdsc.test.api.function.IntPredicate isEven = v -> (v & 1) == 0;
long even = IntStream.of(1, 2, 3).filter(isEven::test).count();
2.3. Code Generation¶
The simple and repetitive code for most predicates in the library is auto-generated from templates.
Generation uses the String Template library.