这个问题会在Haskell QuickCheck best practices (especially when testing type classes)停止的地方找到。
我有一个类和一堆该类的实现。像这样:
import Test.QuickCheck
import Control.Applicative
import Test.Framework
import Test.Framework.Providers.QuickCheck2
class C c where
f :: c -> Int
data A = A Int deriving Show
instance C A where
f (A a) = 2*a
data B = B Int deriving Show
instance C B where
f (B b) = 2*b
我的所有实现都应该满足某个属性。例如:
prop_f_is_even :: C c => c -> Property
prop_f_is_even x = property $ even (f x)
我想为每个实现测试该属性。我可以做这样的事情。 (我正在使用Test.Framework。)
instance Arbitrary A where
arbitrary = A <$> arbitrary
instance Arbitrary B where
arbitrary = B <$> arbitrary
test :: Test
test = testGroup "Whole buncha tests"
[
testProperty "prop_f_is_even - A" (prop_f_is_even :: A -> Property),
testProperty "prop_f_is_even - B" (prop_f_is_even :: B -> Property)
-- continue on for all combinations of properties and implementations
]
但在我的情况下,我有几十个要测试的属性,还有十几个 类,这样的方法容易出错,而且麻烦。 (我犯的一个常见错误是剪切和粘贴测试,但忘记改变 类型名称,所以我最终测试了两次该属性,没有测试B.)
我有一个解决方案,我将在下面发布以防其他人发现它有用。
答案 0 :(得分:3)
这是我的解决方案。
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)
使用这种方法,如果我想添加C
的所有实例都应满足的另一个属性,我只需将其添加到cProperties
。如果我创建另一个C
实例,请将其命名为D
,然后我将dProperties
定义为aProperties
和bProperties
,然后更新easierTest
}。
编辑:
这种方法的一个缺点是cProperties中的所有测试都必须具有类型签名t -> Property
。我自己并没有发现这是一个障碍,因为在我应用这种技术的情况下,我已经 - 由于无关的原因 - 定义了一个包含测试所有数据的类型。
另一个缺点是,在ghci中,我无法再键入,例如:
quickCheck prop_f_is_even
现在我必须键入以下内容:
quickCheck (prop_f_is_even :: A -> Property)