[Haskell-cafe] QuickCheck subsumes unit testing

Donald Bruce Stewart dons at cse.unsw.edu.au
Fri Apr 20 21:54:22 EDT 2007


Just to walk the walk, and not just talk the talk, here's a quick unit
testing 'diff' driver I hacked up for QuickCheck.

When run, it 'diffs' (well, just prints ;-) the incorrect values from
the unit test:

    $ runhaskell T.hs
    sort unit test           : Falsifiable after 0 tests:
    - [1,2,3]
    + [1,3,2]

>From a normal QC specification like:

   prop0 = (sort [3,2,1], [1,3,2])

   main = mapM_ (\(s,a) -> printf "%-25s: " s >> a n) tests
     where
       n = 100
       tests = [("sort unit test", mytest prop0)]

The full driver code is attached. It is just proof of concept, but you
can see how to extend it to be smarter/prettier.

Note that we actually probably want to use SmallCheck here, to prevent
bogus repetition of the test. (I.e. 500 tests all passed, for a unit test).
Note also, the driver would need further extending, since we've changed
the structure of the Testable values.

Cheers,
  Don
-------------- next part --------------
import Data.List
import Text.Printf
import System.IO
import System.Random
import Test.QuickCheck

prop0 = (sort [3,2,1], [1,3,2])

main = mapM_ (\(s,a) -> printf "%-25s: " s >> a n) tests
 where
   n = 100
   tests = [("sort unit test", mytest prop0)]




------------------------------------------------------------------------
-- And a custom driver that `diff's the output

mytest :: (Show a, Eq a) => (a,a) -> Int -> IO ()
mytest (a,b) n = mycheck defaultConfig
    { configMaxTest=n
    , configEvery= \n args -> [] } a b

mycheck :: (Show a , Eq a) => Config -> a -> a -> IO ()
mycheck config a b =
  do rnd <- newStdGen
     mytests config (evaluate (a == b)) a b rnd 0 0 []

mytests :: (Show a , Eq a)
        => Config -> Gen Result -> a -> a -> StdGen -> Int -> Int -> [[String]] -> IO ()
mytests config gen a b rnd0 ntest nfail stamps
  | ntest == configMaxTest config = do done "OK," ntest stamps
  | nfail == configMaxFail config = do done "Arguments exhausted after" ntest stamps
  | otherwise               =
      do putStr (configEvery config ntest (arguments result)) >> hFlush stdout
         case ok result of
           Nothing    ->
             mytests config gen a b rnd1 ntest (nfail+1) stamps
           Just True  ->
             mytests config gen a b rnd1 (ntest+1) nfail (stamp result:stamps)
           Just False ->
             putStr ( "Falsifiable after "
                   ++ show ntest
                   ++ " tests:\n"
                   ++ "- " ++ show a
                   ++ "\n"
                   ++ "+ " ++ show b
                   ++ "\n"
                    ) >> hFlush stdout
     where
      result      = generate (configSize config ntest) rnd2 gen
      (rnd1,rnd2) = split rnd0

done :: String -> Int -> [[String]] -> IO ()
done mesg ntest stamps =
  do putStr ( mesg ++ " " ++ show ntest ++ " tests" ++ table )
 where
  table = display
        . map entry
        . reverse
        . sort
        . map pairLength
        . group
        . sort
        . filter (not . null)
        $ stamps

  display []  = ".\n"
  display [x] = " (" ++ x ++ ").\n"
  display xs  = ".\n" ++ unlines (map (++ ".") xs)

  pairLength xss@(xs:_) = (length xss, xs)
  entry (n, xs)         = percentage n ntest
                       ++ " "
                       ++ concat (intersperse ", " xs)

  percentage n m        = show ((100 * n) `div` m) ++ "%"



More information about the Haskell-Cafe mailing list