2

这个问题涉及到Haskell QuickCheck 最佳实践(尤其是在测试类型类时)停止的地方。

我有一个类和该类的一堆实现。像这样的东西:

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
  ]

但就我而言,我有几十个属性要测试,还有十几个类,所以这种方法容易出错,而且很麻烦。(我犯的一个常见错误是剪切和粘贴测试,但忘记更改类型名称,所以我最终为该属性测试 A 两次,而没有测试 B。)

我有一个解决方案,我会在下面发布,以防其他人觉得它有帮助。

4

1 回答 1

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类似于aPropertiesand bProperties,然后更新easierTest


编辑:这种方法的一个缺点是 cProperties 中的所有测试都必须具有类型签名t -> Property。我自己并没有发现这是一个障碍,因为在我应用这种技术的情况下,我已经 - 由于不相关的原因 - 定义了一个包含所有测试数据的类型。

另一个缺点是,在 ghci 中,我不能再输入,例如:

quickCheck prop_f_is_even

现在我必须输入这样的内容:

quickCheck (prop_f_is_even :: A -> Property)
于 2013-06-19T17:48:53.910 回答