4

我正在测试一个随机生成器,生成我自己类型的实例。为此,我有一个自定义实例Arbitrary

complexGenerator :: (RandomGen g) => g -> (MyType, g)

instance Arbitrary MyType where
    arbitrary = liftM (fst . complexGenerator . mkStdGen) arbitrary

这适用于Test.QuickCheck(实际上Test.Framework)测试生成的值是否具有某些属性。但是,我要检查的属性很多,添加的越多,验证它们所需的时间就越多。

有没有办法使用相同的生成值来测试每个属性,而不是每次都重新生成它们?我显然仍然想看看,在失败时,哪个属性不成立,所以制作一个巨大的属性and并不是最优的。

4

2 回答 2

3

我显然仍然想看看,在失败时,哪个属性不成立,所以制作一个巨大的属性and并不是最优的。

printTestCase您可以在使用制作巨型属性之前使用 标记每个属性conjoin

例如,您认为这是一个坏主意:

prop_giant :: MyType -> Bool
prop_giant x = and [prop_one x, prop_two x, prop_three x]

这将同样有效,但会给你更好的输出:

prop_giant :: MyType -> Property
prop_giant x = conjoin [printTestCase "one" $ prop_one x,
                        printTestCase "two" $ prop_two x,
                        printTestCase "three" $ prop_three x]

(话虽如此,我自己从未使用过这种方法,只是假设它会起作用;conjoin可能出于某种原因在文档中标记为实验性的。)

于 2013-03-10T10:36:49.867 回答
1

结合投票的答案,我发现有用的是使用带有 Writer monad 的 Reader 转换器:

type Predicate r = ReaderT r (Writer String) Bool

在这种情况下,Reader “共享环境”是经过测试的输入。然后你可以像这样编写属性:

inv_even :: Predicate Int
inv_even = do
  lift . tell $ "this is the even invariant"
  (==) 0 . flip mod 2 <$> ask 

toLabeledProp :: r -> Predicate r -> Property
toLabeledProp cause r =
  let (effect, msg) = runWriter . (runReaderT r) $ cause in
    printTestCase ("inv: " ++ msg) . property $ effect

并结合:

fromPredicates :: [Predicate r] -> r -> Property
fromPredicates predicates cause =
  conjoin . map (toLabeledProp cause) $ predicates

我怀疑还有另一种方法涉及类似于这里的 Either 或 WriterT 的东西——它可以简洁地将不同类型的谓词组合成一个结果。但至少,这允许记录根据输入值强加不同后置条件的属性。

编辑:这个想法催生了一个图书馆:http: //github.com/jfeltz/quickcheck-property-comb

于 2013-10-14T01:46:20.873 回答