Haskell Test Framework

Haskell Test Framework:

The Haskell Test Framework (HTF) supplies unit tests via HUnit and
property tests via QuickCheck. The following code describes some of the
functionality one may find in the test suite for the Attenuations project.
Some other useful tutorials can be found here:

At the top level one can safely disentangle functionality from testing
by creating a RayTracer directory for the former and a Tests directory
for the latter. Now by creating a TestMain.hs with the following lines,
one can import the tests and the modules independently as-well-as define
sub-suites to run individually.

{-# OPTIONS_GHC -F -pgmF htfpp #-}

module AttenuationTest where
import RayTracer.RayLength
import RayTracer.Lattice
import RayTracer.Rhythm
import Test.Framework

import {-@ HTF_TESTS @-} Tests.IndexerTests
import {-@ HTF_TESTS @-} Tests.SymmetryTests
import {-@ HTF_TESTS @-} Tests.XRegionTests

main = htfMain htf_importedTests

symmetryTests = htfMain htf_Tests_SymmetryTests_thisModulesTests
indexerTests = htfMain htf_Tests_IndexerTests_thisModulesTests
xRegionTests = htfMain htf_Tests_XRegionTests_thisModulesTests

Some Subtleties.

QuickCheck versus HUnit:

From ghci one can perform a quickCheck on any property test, prop_ prefixed,
by running something akin to quickCheck prop_someProperty. To run a specific unit
test, simply call the test method directly:

test_rabbits :: IO ()
test_rabbits = do
  let rhythm = take 15 $ rabbits (5,3)
  assertEqual rhythm ".rLrrLr.rLrrLr."

In some cases, one may wish to build a small unit test suite:

import Test.HUnit

test1 = TestCase test_rabbits
test2 = TestCase test_someotherFunction

tests = TestList [TestLabel "rabbits" test1,
                  TestLabel "another" test2]

runSmallSuite = runTestTT tests

Which then returns something like:

Cases: 2  Tried: 2  Errors: 0  Failures: 0
Counts {cases = 2, tried = 2, errors = 0, failures = 0}

Limiting the Range of Test Data:

It may often be the case that a functions domain of validity is limited
to a small subset of its range and testing outside of that range isn’t
very useful. The choose function makes it possible to limit the range
of test values while maintaining statistical randomness. For instance,
verifying that cos (π/2-θ) is the same as sin θ for values between
0 and 2π can be tested:

tol :: Double -> Integer
tol d = round $ d * 10^12

prop_cosToSin = do
  θ <- choose (0, 2*pi)
  return $ (tol.cos) (pi/2 - θ) == (tol.sin) θ

where tol allows for some wiggle room in the approximation.

Playing and then Replaying a test.

Occasionally a test will fail. Along with the error when a test fails
will be a Replay string allowing intentional seeding when replaying
a given test.

replayArg = "Just (TFGenR 15067B55359906C0776B9C0A73ACEE7D9C124B4AE3DAC3AFCB451E04B1EF7BD1 0 31 5 0,28)"

Now, to replay the failed prop_cosToSin test above one only needs to supply
the replayArg above in a new method, prop_cosToSinReplay:

prop_cosToSinReplay =
  withQCArgs (\a -> a { replay = read replayArg })

main = htfMain htf_Tests_SymmetryTests_thisModulesTests

Fairly General Algebraic Testing:

One of the coolest examples I have seen this far concerns testing
algebraic properties like the associativity of function composition
By importing Text.Show.Functions one can even test this property
with amazingly clean style:

import Text.Show.Functions

prop_ComposeAssoc f g h x =
  ((f . g) . h) x == (f . (g . h)) x
  where types = [f, g, h] :: [Int->Int]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s