· haskell

Haskell: My first attempt with QuickCheck and HUnit

As I mentioned in a blog post a few days I’ve started learning QuickCheck with the test-framework package as suggested by David Turner.

I first needed to install test-framework and some dependencies using cabal:


> cabal install test-framework
> cabal install test-framework-quickcheck
> cabal install test-framework-hunit

I thought it’d be interesting to try and write some tests around the windowed function that I wrote a few months ago:

Windowed.hs


module Windowed (windowed) where

windowed :: Int -> [a] -> [[a]]
windowed size [] = []
windowed size ls@(x:xs) = 
  if length ls >= size then (take size ls) : windowed size xs 
  else windowed size x

I wrote my first property like so:


import Windowed

import Test.Framework (defaultMain, testGroup)
import Test.Framework.Providers.QuickCheck (testProperty)
import Test.QuickCheck

main = defaultMain tests

tests = [
  testGroup "windowed" [
    testProperty "should not window if n is bigger than list size" prop_nothing_if_n_too_large
  ]
] 
						  	
prop_nothing_if_n_too_large n xs = n > length xs ==>  windowed n xs == []

And tried to compile the file:


ghc -package test-framework -package test-framework-quickcheck -threaded WindowedQC.hs -o Windowed

Which resulted in this compilation error:


WindowedQC.hs:16:17:
    No instance for (QuickCheck-1.2.0.1:Test.QuickCheck.Testable
                       (Gen Prop))
      arising from a use of `testProperty'
    Possible fix:
      add an instance declaration for
      (QuickCheck-1.2.0.1:Test.QuickCheck.Testable (Gen Prop))

According to the test-framework home page this error happens when we have more than one version of QuickCheck installed so we need to tell ‘ghc’ which one to use:


ghc -package test-framework -package test-framework-quickcheck -package QuickCheck-1.2.0.1 -threaded WindowedQC.hs -o Windowed

That solved the first compilation problem but I still had another:


WindowedQC.hs:16:17:
    Ambiguous type variable `a0' in the constraints:
      (Arbitrary a0) arising from a use of `testProperty'
                     at WindowedQC.hs:12:17-28
      (Show a0) arising from a use of `testProperty'
                at WindowedQC.hs:12:17-28
      (Eq a0) arising from a use of `prop_nothing_if_n_too_large'
              at WindowedQC.hs:12:80-106
    Probable fix: add a type signature that fixes these type variable(s)

The way to solve this problem is to explicitly state which types will be passed to the property like so:


prop_nothing_if_n_too_large n xs = n > length xs ==>  windowed n xs == []
    where types = (xs :: [Int], n :: Int)

I eventually ended up with the following:

WindowedQC.hs


import Windowed

import Test.Framework (defaultMain, testGroup)
import Test.Framework.Providers.QuickCheck (testProperty)
import Test.QuickCheck

import Test.Framework.Providers.HUnit
import Test.HUnit

main = defaultMain tests

tests = [
  testGroup "windowed" [
    testProperty "should not window if n is bigger than list size" prop_nothing_if_n_too_large,
    testProperty "should create sub arrays the size of window" prop_size_of_sub_arrays_is_n,
    testProperty "should group adjacent items into windows" prop_groups_adjacent_items,                
    testCase "should window a simple list" test_should_window_simple_list
  ]
] 

prop_groups_adjacent_items n xs = n < length xs ==>  
                                  not (null xs) ==>
                                  n > 0 ==> (last $  last $ windowed n xs) == last xs
  where types = (xs :: [Int], n :: Int)			
				  	
prop_nothing_if_n_too_large n xs = n > length xs ==>  windowed n xs == []
  where types = (xs :: [Int], n :: Int)

prop_size_of_sub_arrays_is_n n xs =  n > 0 ==> all (\subArray -> length subArray == n)  (windowed n xs)  
  where types = (xs :: [Int], n :: Int)
	
test_should_window_simple_list = 
  windowed 2 [1..10] @?= [[1,2], [2,3], [3,4], [4,5], [5,6], [6,7], [7,8], [8,9], [9,10]]

I initially only wrote QuickCheck properties but I wasn’t satisfied that just passing the properties I’ve written would guarantee the function is working as expected.

The first property does a simple check that the last item from the windowed function is the same as the last item in the list assuming that the list isn’t empty, that we’re using a positive n value and our window size is smaller than the list size.

The second checks that we end up with an empty list if we try to window to a size bigger than the list and the third checks that each of the sub arrays is the correct size.

I wasn’t sure how to write a QuickCheck property that would test the values of the individual sub arrays so I ended up writing a HUnit test instead.

If you know how I could achieve the same thing using QuickCheck please let me know.

We can then run all the tests like so:


> ./Windowed                                                                                                                    
windowed:

  should not window if n is bigger than list size: [OK, passed 100 tests]

  should create sub arrays the size of window: [OK, passed 100 tests]

  should group adjacent items into windows: [OK, passed 100 tests]

  should window a simple list: [OK]

          Properties  Test Cases  Total      
  Passed  3           1           4          
  Failed  0           0           0   
  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket