使用快速检查的上下文敏感生成

context-sensitive generation using quick check

我想根据某种 "context" 生成随机术语,我想知道是否可以使用快速检查。基本上我想传递一个额外的数据类型,以便任意函数可以根据额外的参数生成术语......这可以用快速检查还是我应该写我自己的 Gen 定义?

arbitrary 内部执行此操作是可能的,尽管不是很理智。但是如果你跳出arbitrary,你可以字面上只是传递一个额外的参数。

-- do whatever you want inside the implementation of these two
chooseIntRange :: Context -> Int
updateContext :: Int -> Context -> Context

arbitraryIntWithContext :: Context -> Gen (Context, Int)
arbitraryIntWithContext ctx = do
    n <- choose (0, chooseIntRange ctx)
    return (n, updateContext n ctx)

可以通过 StateT 稍微减轻上下文的管道,例如

-- do whatever you want inside the implementation of this
chooseIntRangeAndUpdate :: MonadState Context m => m Int

arbitraryIntStateT :: StateT Context Gen Int
arbitraryIntStateT = do
    hi <- chooseIntRangeAndUpdate
    lift (choose (0, hi))

虽然 Daniel Wagner 为 QuickCheck (+1) 提供了一个很好的答案,但它也突出了 QuickCheck 的一个弱点。在 QuickCheck 中,使用 Arbitrary 的实例编写属性,但由于其设计,Arbitrary 不是一元的。

Daniel Wagner 分享的解决方法是 Gen,另一方面, monadic,因此您可以使用 [=15 编写上下文相关的代码=] 符号。缺点是虽然您可以将 Gen a 转换为 Arbitrary a,但您要么必须提供自定义 shrink 实现,要么放弃收缩。

用于基于 属性 的测试的替代库 Hedgehog 的设计方式是属性本身是一元的,这意味着您可以编写整个 属性 并简单地在测试代码本身中嵌入特定上下文的临时值生成(包括收缩):

propWithContext :: Property
propWithContext = property $ do
  ctx <- forAll genContext
  n <- forAll $ Gen.integral $ Range.linear 0 $ chooseIntRange ctx
  let ctx' = updateContext n ctx

  -- Exercise SUT and verify result here...

这里,genContextContext类型的自定义生成器,类型为

genContext :: MonadGen m => m Context