I've partially solved the problem I had with testing class methods by creating a Test monad which threads the correct type through the test. The end result is unfortunately rather verbose but I don't want to get bogged down with tweaking it.
The first parameter of TestM controls which instance will be used. The Poly* constructors are used for values which need to be fixed to this instance.
data TestM m a where
Arb :: forall a m. (Arbitrary a, Show a) => TestM m a
Gen :: forall m a. Show a => Gen a -> TestM m a
Test :: forall a b m. Testable a => a -> TestM m b
Poly :: m -> TestM m m
PolyArb :: forall m. (Arbitrary m, Show m) => TestM m m
PolyGen :: forall m. (Show m) => Gen m -> TestM m m
Check :: forall m. Bool -> TestM m Bool
type Test m = TestM m ()
instance Testable (TestM a b) where
property (Test a) = property a
property t = error "Not testable"
Unfortunately QuickCheck doesnt expose a way to combine two Testable values. I want something like 'and :: (Testable a, Testable b) => a -> b -> Property'. Without it Im restricted to only one real Test per TestM.
instance Monad (TestM m) where
return a = error "No returning in TestM (yet)"
Arb >>= f = Test f
(Gen g) >>= f = Test $ forAll g f
(Poly m) >>= f = Test $ f m
PolyArb >>= f = Test f
(PolyGen g) >>= f = Test $ forAll g f
(Test a) >> f = error "Can only have one test per TestM "
(Check b) >> f = Test (b ==> f)
(Test a) >> m = error "Can only have one test per TestM"
_ >> m = m
With this in place I can write the following silly test. Note that I dont have to locally specify the type of 'thing'.
test :: (Arbitrary a, Show a) => Test a
test = do
i <- Arb
thing <- PolyArb
Test $ (length $ show thing) < (abs i) + 10
Prelude TestM Test.QuickCheck> test (showTest :: Test Int)
OK, passed 100 tests.
Prelude TestM Test.QuickCheck> test (showTest :: Test [Int])
Falsifiable, after 19 tests:
-1
[10,0,-2,6,11]
This is useful when writing tests like:
prop_delete = do
m <- PolyGen nonEmptyMap
k <- Gen $ keyOf m
equivAre m (delete k) (L.deleteBy (\ a b -> key a == key b) (k,undefined))
All my tests are now of the type 'GT map k => Test map' (give or take 3 lines of constraints). So it is with great pleasure that I can finally write:
runTests n = do
let run ps = mapM_ ( \ (prop, name) -> do
putStr name
putStr " : "
check (config n) prop ) $ zip ps propNames
run (props :: [Test (IntGT Int)])
run (props :: [Test (BoolGT Int)])
run (props :: [Test (UnitGT Int)])
run (props :: [Test (EitherGT IntGT BoolGT Int)])
run (props :: [Test (ListGT IntGT Int Int)])
run (props :: [Test (ListGT BoolGT Bool Int)])
run (props :: [Test (ListGT UnitGT () Int)])
run (props :: [Test (ListGT (EitherGT IntGT BoolGT) (Either Int Bool) Int)])
This will be incredibly helpful as the number of instances grows.
After much discussion I now have a fair idea how the map interface will look. There were a lot of nice ideas posted on the libraries list so my aim for this week is to try them all out and by the end of the week produce a stable api. I'm really hoping that the nice combinator based designs will be fast enough - they require so much less typing.
Monday, 30 June 2008
Subscribe to:
Post Comments (Atom)
0 comments:
Post a Comment