Have you ever written a unit test with magic numbers in and felt bad? For
example, given a C++ class that simulates stock prices, Simulation, you would expect a starting price of zero to
stay at zero. Let’s write a test for this using Catch
TEST_CASE("simulation starting at 0 remains at 0", "[Property]")
{
const double start_price =
0.0;
const double drift = 0.3;//or
whatever
const double volatility = 0.2;//or
whatever
const double dt = 0.1;//or
whatever
const unsigned int seed = 1; //or whatever
Simulation price(start_price, drift, volatility, dt, seed);
REQUIRE(price.update()
== 0.0);
}
Oh dear; magic numbers. That sinking feeling when you don’t
know or care what values some variables take. The comments hint at the
unhappiness. You could write a few more tests cases with other numbers, or use
a parameterised approach. Trying every possible double or int would be extreme, and make the unit tests slow. Unit tests should be fast, so we’d best not. We
could try some random variables instead of the magic numbers. This might lead
to cases that sometimes fail, and unit tests should provide repeatable results,
so we’d best not.
Oh dear. If only we had some random magic to help. We need something that allows us to test that properties hold for a variety of cases. We don’t want to hand roll lots of ad-hoc test cases ourselves. If we generate random test cases we need the results to be clearly reported so we know what went wrong if something fails. We need property-based testing. Good news! Haskell got there long before us.
QuickCheck “is a tool for
testing Haskell programs automatically. The programmer provides a specification
of the program, in the form of properties which functions should satisfy, and
QuickCheck then tests that the properties hold in a large number of randomly
generated cases.” [See the manual] You
define a property, such as reversing a reversed list gives the original list
prop_RevRev xs = reverse (reverse xs) == xs
where types = xs::[Int]
Main> quickCheck prop_RevRev
OK, passed 100 tests.
If a property doesn’t
hold, quickCheck reports the case or “counter-example” for which it does not
hold. Instead of my initial “example-based” test I can now test my property
holds generally. Since the cases are randomly generated rather than exhaustive
I may still miss problems, but look how much shorter the code was.
Wait a moment! I was trying
to test some C++ and got distracted by Haskell. The good news is ports of
QuickCheck exist for various languages. For example, F# has FSCheck Python has Hypothesis and, C++
being C++, has various versions. I have tried Legiasoft’s QuickCheck and showed my initial
attempts at the #ACCU2015 conference.
A recent blog from
Spotify drew my attention to
RapidCheck. This claims to integrate
with Boost test and Google Test/Mock though I haven't tried it yet. I wonder if I can make it play nicely with
Catch. I will report back. Another interesting feature it supports is stateful
based testing, based on Erlang’s port of QuickCheck. Since this started with
Haskell, many frameworks need *pure* functions. Once in a while, some of us are
not quite as pure as we'd like, so I can imagine this being very useful.
I hope this has sparked some excitement about new ways of testing your code. Next time someone asks “Unit tests or integration tests?” say “Yes, and also property-based tests”.
I hope this has sparked some excitement about new ways of testing your code. Next time someone asks “Unit tests or integration tests?” say “Yes, and also property-based tests”.
No comments:
Post a Comment