Here is a short introduction on how to write specifications and test them with JCheck.
JCheck uses JUnit to function and any JUnit test case is essentially a JCheck test case and can be run with JCheck (and should work in the same way). You should be able to use JCheck in your favorite IDE or testing tool that supports JUnit 4 just as if it were JUnit. All one needs to do is to add the jcheck.jar to the classpath and let the fun begin!
The JUnit Cookbook provides the following simple example of a JUnit test.
class SimpleTest {
@Test public void simpleAdd() {
Money m12CHF= new Money(12, "CHF");
Money m14CHF= new Money(14, "CHF");
Money expected= new Money(26, "CHF");
Money result= m12CHF.add(m14CHF);
assertTrue(expected.equals(result));
}
}
Such a test is, of course, far from satisfactionary. What one
probably wants to test is that Money.add(Money)
works
for all possible values. Such a test can of course be written, but
would take quite some time to execute. A solution would be to
test it with a (large) number of random values, if those tests are
successful, then the code is probably sound (or at least it is more
probable than with a single test case).
With JCheck this is very simple, the whole point beeing easy random
testing. Simply add parameters to the method and a JUnit
@RunWith
annotation telling JUnit to use JCheck's runner
instead of the standard one.
@RunWith(org.jcheck.runners.JCheckRunner.class)
class SimpleTest {
@Test public void simpleAdd(int i, int j) {
Money miCHF= new Money(i, "CHF");
Money mjCHF= new Money(j, "CHF");
Money expected= new Money(i+j, "CHF");
Money result= miCHF.add(mjCHF);
assertTrue(expected.equals(result));
}
}
Now the test method will be run a large number of times with random integers as parameters. As simple as that!
Sometimes one needs to limit the possible input, for instance, if you are writing a sorting algorithm and wants to make sure it doesn't lose any elements you might want to test only arrays with sizes larger than one element.
@RunWith(org.jcheck.runners.JCheckRunner.class)
class SimpleTest {
@Test public void simpleDivide(int[] somearray) {
imply(somearray.length > 1);
int expected= somearray.length;
int result= MySort.sort(somearray).length;
assertEquals(expected, result);
}
}
The call to imply will make sure that the test only is performed on input for which the implication holds. However some caution is needed - JCheck is random and will only try to generate new input parameters instead of failed ones a fixed number of times, you might get an "Arguments exhausted" exception if your selection is too narrow. In such a case it is best to write your own generator.
In some instances you need a very specific kind of input, or input of a type that JCheck doesn't support by default. In such a case you need to write your own generator.
For instance you might have an algorithm that only ever uses
byte-arrays of size 2. In such a case using imply()
would most certainly lead to an "Arguments exhausted", or at least
take a very long time to execute. Better then to create a custom
generator.
class CustomByteGen implements Gen<byte[]> {
public byte[] arbitrary(Random random, long size)
{
byte[] arr = new byte[2];
random.nextBytes(arr);
return arr;
}
}
@RunWith(org.jcheck.runners.JCheckRunner.class)
class SimpleTest {
@Generator(klass=byte[].class,
generator=CustomByteGen.class)
@Test public void byteArrTest(byte[] somearray) {
// some kind of test
}
}
Using the @Generator
annotation you can give a
specific generator for a specific class, either on class level (used
by all methods by default) or on method level (overrides any class
level generator). For methods one can also specify a parameter
position. If many different special generators has to be used
one must use the @UseGenerators
annotation with a list
of generators.
@RunWith(org.jcheck.runners.JCheckRunner.class)
class SimpleTest {
@UseGenerators({
@Generator(position=0,
generator=CustomByteGen.class),
@Generator(position=1,
generator=OtherCustomByteGen.class)})
@Test public void byteArrTest(byte[] somearray,
byte[] someother) {
// some kind of test
}
}
There exist a third way to control input - one that also allows to
control how many tests are done, namely the @Configuration
annotation. This annotation sets the maximum (and minimum) size of
generated objects and how many tests are done (exluding those discarded
by imply()
).
The code above limits numbers to between -5 and 5 (and array sizes
etc) and will run each test 10 times, except for method test2
that will be run a 100 times.
@RunWith(org.jcheck.runners.JCheckRunner.class)
@Configuration(tests=10, size=5)
class SimpleTest {
@Test public void test1(int i) {
// some kind of test
}
@Configuration(tests=100)
@Test public void test2(int i) {
// some kind of test
}
}