Popper extends JUnit to allow you to specify theories. Theories are assertions about your code's behavior that may be true over infinite sets of input values. You might find it useful to pose theories about your Groovy code too.
Let's consider how we might test the following class (example taken from the Popper web site):
With traditional JUnit code, we might test it as follows:
This tests the method for one
amount value and one
m value. Next steps might be to triangulate so that additional values are also tested. In general though, it might be difficult to know when you have done enough values (when to stop) and also what invariants of your class may hold if you simply keep adding more tests without sufficient refactoring. With these factors in mind, Popper provides facilities to make invariants and preconditions of your classes obvious as well as providing an extensible framework for adding new test values.
Here is how you might use Popper to test the above class. First, we have avoided using Hamcrest style assertions in our Groovy code. Groovy's built-in
assert method usually allows such assertions to be expressed very elegantly without any additional framework. We'll create a small helper class to allow Groovy-style assertions to be used for method pre-conditions:
Now, our test becomes:
We have added an additional log variable to this example to explain how Popper works. By default, Popper will use any public fields in our test as test data values
VAL4 in our example. It will determine all combinations of the available variables and call the
multiplyIsInverseOfDivide() for each combination. This is a very crude way to select test instance values but works for simple tests like this one.
You should also note the
assume statement. In our example, we haven't catered for
m being 0 which would result in a divide by zero error. The assume statement allows this method precondition to be made explicit. When Popper calls the test method, it will silently ignore any test data combinations which fail the method preconditions. This keeps the preconditions obvious and simplifies creating test data sets.
Here is the output from running this test:
We wouldn't normally recommend sending this kind of information to standard out when running your test, but here it is very illustrative. Note that all four test values have been used for the
amount variable but only three values have been used for
m. This is exactly what we want here.
Popper supports an extensible framework for specifying more elaborate algorithms for selecting test data. Instead of the public variables, we can define our own parameter supplier. Here is one (coded in Java) which supplies data between a
first value and a
last value. First the annotation definition:
And the backing supplier:
Now our Groovy test example could become:
When run, this yields:
The supplied test values for the test method are
(-4, -2), (-4, -1), (-4, 0), ..., (2, 5). The data where m is equal to 0 will be skipped as soon as the
assume statement is reached.
We can also Groovy to make the bowling example a little more succinct: