use*_*ame 15 testing monads automated-tests haskell smallcheck
我正在尝试使用SmallCheck来测试Haskell程序,但我无法理解如何使用该库来测试我自己的数据类型.显然,我需要使用Test.SmallCheck.Series.但是,我发现它的文档非常混乱.我对菜谱式解决方案和逻辑(monadic?)结构的可理解解释感兴趣.以下是我的一些问题(全部相关):
如果我有数据类型data Person = SnowWhite | Dwarf Integer,我smallCheck该如何解释有效值是Dwarf 1通过Dwarf 7(或SnowWhite)?如果我有一个复杂的FairyTale数据结构和构造函数makeTale :: [Person] -> FairyTale,并且我想smallCheck使用构造函数从Person-s列表中制作FairyTale-s怎么办?
我设法quickCheck通过使用Control.Monad.liftM类似功能的明智应用程序来完成这样的工作,而不会让 我的手太脏makeTale.我无法想办法解决这个问题smallCheck(请向我解释一下!).
什么是类型之间的关系Serial,Series等等?
(可选)有什么意义coSeries?我如何使用Positive来自的类型SmallCheck.Series?
(可选)在smallCheck的上下文中,任何阐明应该是monadic表达式的逻辑是什么,以及什么只是常规函数,将会受到赞赏.
如果有任何介绍/教程使用smallCheck,我会很感激一个链接.非常感谢你!
更新:我应该补充一点,我找到的最有用和可读的文档smallCheck是本文(PDF).第一眼看我在那里找不到我的问题的答案; 它更像是一个有说服力的广告而不是教程.
更新2:我把关于Identity在类型Test.SmallCheck.list和其他地方出现的怪异问题转移到一个单独的问题上.
J. *_*son 17
注意:此答案描述了SmallCheck的1.0之前的版本.有关SmallCheck 0.6和1.0之间的重要区别,请参阅此博客文章.
SmallCheck与QuickCheck类似,它在可能类型的某些部分空间中测试属性.不同之处在于它试图详尽地枚举一系列所有 "小"值而不是任意小值的子集.
正如我所暗示的,SmallCheck Serial就像QuickCheck 一样Arbitrary.
现在Serial很简单:一个Serial类型a有一个way(series)来生成一个Series只是函数的类型Depth -> [a].或者,为了解压缩,Serial对象是我们知道如何枚举一些"小"值的对象.我们还给出了一个Depth参数来控制我们应该生成多少个小值,但让我们忽略它一分钟.
instance Serial Bool where series _ = [False, True]
instance Serial Char where series _ = "abcdefghijklmnopqrstuvwxyz"
instance Serial a => Serial (Maybe a) where
series d = Nothing : map Just (series d)
Run Code Online (Sandbox Code Playgroud)
在这些情况下,我们只是忽略Depth参数,然后枚举每种类型的"所有"可能值.我们甚至可以为某些类型自动执行此操作
instance (Enum a, Bounded a) => Serial a where series _ = [minBound .. maxBound]
Run Code Online (Sandbox Code Playgroud)
这是一种非常简单的测试属性的方法 - 逐字测试每一个可能的输入!显然,至少存在两个主要缺陷:(1)无限数据类型在测试时将导致无限循环;(2)嵌套类型导致示例的大量空间通过查看.在这两种情况下,SmallCheck都非常快.
这就是Depth参数的重点 - 它让系统让我们保持Series小.从文档中,Depth是
生成的测试值的最大深度
对于数据值,它是嵌套构造函数应用程序的深度.
对于功能值,它既是嵌套案例分析的深度,也是结果的深度.
所以让我们重复我们的例子,让它们变小.
instance Serial Bool where
series 0 = []
series 1 = [False]
series _ = [False, True]
instance Serial Char where
series d = take d "abcdefghijklmnopqrstuvwxyz"
instance Serial a => Serial (Maybe a) where
-- we shrink d by one since we're adding Nothing
series d = Nothing : map Just (series (d-1))
instance (Enum a, Bounded a) => Serial a where series d = take d [minBound .. maxBound]
Run Code Online (Sandbox Code Playgroud)
好多了.
那是什么coseries?就像coarbitrary在ArbitraryQuickCheck的类型类中一样,它允许我们构建一系列"小"函数.请注意,我们在输入类型上编写实例 - 结果类型在另一个Serial参数中传递给我们(我在下面调用results).
instance Serial Bool where
coseries results d = [\cond -> if cond then r1 else r2 |
r1 <- results d
r2 <- results d]
Run Code Online (Sandbox Code Playgroud)
这些需要更多的聪明才能写,我实际上会引用你使用alts我将在下面简要描述的方法.
那么怎样才能使一些Series的PersonS' 这部分很容易
instance Series Person where
series d = SnowWhite : take (d-1) (map Dwarf [1..7])
...
Run Code Online (Sandbox Code Playgroud)
但是我们的coseries函数需要生成从Persons到其他东西的所有可能函数.这可以使用altsNSmallCheck提供的一系列功能来完成.这是编写它的一种方法
coseries results d = [\person ->
case person of
SnowWhite -> f 0
Dwarf n -> f n
| f <- alts1 results d ]
Run Code Online (Sandbox Code Playgroud)
的基本思想是,altsN results产生Series的N从进制函数N值与Serial实例的Serial实例Results.因此我们使用它来创建一个函数,从[0..7],一个先前定义的Serial值,到我们需要的任何值,然后我们将Persons 映射到数字并传递给它们.
现在我们有了一个Serial实例Person,我们可以用它来构建更复杂的嵌套Serial实例.对于"实例",如果FairyTale是Persons 的列表,我们可以使用Serial a => Serial [a]实例与我们的Serial Person实例一起轻松创建Serial FairyTale:
instance Serial FairyTale where
series = map makeFairyTale . series
coseries results = map (makeFairyTale .) . coseries results
Run Code Online (Sandbox Code Playgroud)
(与每个函数(makeFairyTale .)组合生成,这有点令人困惑)makeFairyTalecoseries