This is my solution.
cProperties :: C t => String -> [(String, t -> Property)]
cProperties s =
[
("prop_f_is_even: " ++ s, prop_f_is_even)
-- plus any other tests that instances of C should satisfy
]
makeTests :: (Arbitrary t, Show t) => [(String, t -> Property)] -> [Test]
makeTests ts = map (\(s,t) -> testProperty s t) ts
aProperties :: [(String, A -> Property)]
aProperties = cProperties "A"
bProperties :: [(String, B -> Property)]
bProperties = cProperties "B"
easierTest :: Test
easierTest =
testGroup "tests" (makeTests aProperties ++ makeTests bProperties)
With this approach, if I want to add another property that all instances of C
should satisfy, I just add it to cProperties
. And if I create another instance of C
, call it D
, then I define dProperties
similarly to aProperties
and bProperties
, and then update easierTest
.
EDIT:
One disadvantage of this approach is that all tests in cProperties have to have the type signature t -> Property
. I myself have not found this to be a hindrance because in cases where I apply this technique, I have already -- for unrelated reasons -- defined a type that encompasses all of the data for a test.
Another disadvantage is that, in ghci, I can no longer type, for example:
quickCheck prop_f_is_even
Now I have to type something like this:
quickCheck (prop_f_is_even :: A -> Property)