[Haskell-cafe] TDD in Haskell

Neil Brown nccb2 at kent.ac.uk
Tue May 25 08:54:25 EDT 2010


On 25/05/10 12:36, Ionut G. Stan wrote:
> Hi,
>
> I'm doing TDD in pretty much all of the languages that I know, and I 
> want to introduce it early in my Haskell learning process. I wonder 
> though, if there's some established process regarding TDD, not unit 
> testing.
>
> I've heard of QuickCheck and HUnit, but I've also read that QuickCheck 
> is used mostly for testing pure code, while HUnit for impure code?

I tend to use HUnit more than QuickCheck (even for pure code), but that 
may be a reflection of the projects I've worked on.  Reasons why I have 
used HUnit:

- Writing an Arbitrary instance for a data type wasn't worth the 
effort.  When writing a nanopass compiler, it seemed like too much 
effort to write an Arbitrary instance to produce a valid fragment of AST 
for that particular pass (given that each pass would probably need a 
different instance).  Problems with the instance include:

   * If I allowed every instance to generate the full variety of AST 
elements, the chances of generating an AST fragment that tested the pass 
in question would be vanishingly small, which would require customising 
the instance (or adding generation conditions) in fiddly ways for each 
pass to make sure I got good coverage.

   * For most passes, the AST must obey many pre-conditions, such as all 
variables being declared before use, all expressions type-checking, 
passing various safety/sanity checks and so on.  All these (and 
different sets of them) would need to be considered for each Arbitrary 
instance, which adds a lot of effort to the instance generation, when I 
can easily craft a few targeted pieces of input myself.

- With some functions I can provide an exhaustive check of all inputs, 
for which QuickCheck is ill-suited.  This would suit SmallCheck and 
LazySmallCheck, but often it's just easier to write something like: 
assertTrue $ and [checkResult x (f x) | x <- allInputs] than to use the 
other frameworks.

- For QuickCheck 1, testing of things like IO actions was very difficult 
(I think unsafePerformIO was needed?) but QuickCheck 2 is much better in 
that regard once you've gone the hang of it.

More generally, I often think it is less effort to test the corner/edge 
cases carefully with HUnit than it is to express the properties of the 
function (and the properties are almost always noticeably longer than 
the function being tested) and construct an Arbitrary instance that 
produces valid input.  Perhaps I just haven't worked on the right 
projects for QuickCheck or haven't got into the property-based testing 
mindset, though.

Thanks,

Neil.


More information about the Haskell-Cafe mailing list