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 })
prop_cosToSin
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]