Is there a way to test implementations of an interface? (Test a Contract)
brabster opened this issue · 4 comments
Hi there, thanks for this great project!
I've written an interface for an algorithm and written a test for the invariants of the algorithm. I'd like to run the test over different implementations without copy-pasting but I'm not sure how. Maybe think abot a sort algorithm as an example - I want to test different implementations and ensure that the output has the same elements as the input and ends up ordered correctly.
I don't mind if I have to maintain a set of the implementations I want to test rather than having them automagically discovered.
I thought maybe JUnit's Parameterized
would do the job but I'm not sure how to use that with quickcheck?
Sorry if the answer's somewhere staring me in the face!
Thanks
@brabster Thanks for your interest in junit-quickcheck!
Could you provide a rough sketch of the classes you're looking to test? It may help clarify in my head the best strategy for doing the tests you want.
Hi there, thanks for picking this up.
I have a BiFunction<Float, Float, Float>
To be a FooCalculator
(which used to be an interface but now is just a concept that we understand - could be an interface again if it helps), an implementation must be associative, commutative, and always produce a value between 0 and 1 where the inputs are between 0 and 1.
An implementation that works might be (a, b) -> 0.5
One that works sometimes but not others might be (a, b) -> a + b
So I want to define a FooCalculatorImplementationTest
that checks these properties for any implementation of BiFunction I want. I reckon I'll need to provide the functions I want to check.
Does that help? Please let me know if you need anything else!
@brabster You're describing what sounds like a contract test: given some implementation of an interface, verify that certain properties hold when invoking its methods with random arguments (subject to any constraints on the args).
How would something like this suit your needs?
import java.util.function.BiFunction;
import com.pholser.junit.quickcheck.Property;
import com.pholser.junit.quickcheck.generator.InRange;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public interface FooCalculatorContract {
BiFunction<Float, Float, Float> subject();
@Property default void commutative(float a, float b) {
BiFunction<Float, Float, Float> f = subject();
assertEquals(f.apply(a, b), f.apply(b, a));
}
@Property default void associative(float a, float b, float c) {
BiFunction<Float, Float, Float> f = subject();
assertEquals(
f.apply(f.apply(a, b), c),
f.apply(a, f.apply(b, c)));
}
@Property default void resultsInRange(float a, float b) {
float result = subject().apply(a, b);
assertThat(result, greaterThanOrEqualTo(0F));
assertThat(result, lessThanOrEqualTo(1F));
}
}
Then implement the interface, e.g.:
import java.util.function.BiFunction;
import com.pholser.junit.quickcheck.runner.JUnitQuickcheck;
import org.junit.runner.RunWith;
@RunWith(JUnitQuickcheck.class)
public class FloatAdditionProperties implements FooCalculatorContract {
@Override public BiFunction<Float, Float, Float> subject() {
return (a, b) -> a + b;
}
}
Note that the float
generator in junit-quickcheck-generators by default emits values in the interval [0, 1].
Hey great! Contract test - that's the phrase I needed! And that solution looks spot on.
I'll see if I can PR something useful to your docs about this scenario to say thanks!