为类定义一套测试

时间:2013-06-19 17:48:54

标签: haskell quickcheck

这个问题会在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.)

我有一个解决方案,我将在下面发布以防其他人发现它有用。

1 个答案:

答案 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定义为aPropertiesbProperties,然后更新easierTest }。


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

另一个缺点是,在ghci中,我无法再键入,例如:

quickCheck prop_f_is_even

现在我必须键入以下内容:

quickCheck (prop_f_is_even :: A -> Property)