注意:这个答案描述了 SmallCheck 的 1.0 之前的版本。有关SmallCheck 0.6 和 1.0 之间的重要区别,请参阅此博客文章。
SmallCheck 与 QuickCheck 类似,它在可能类型的空间的某些部分上测试属性。不同之处在于它试图详尽地枚举一系列所有“小”值,而不是小值的任意子集。
正如我所暗示的,SmallCheckSerial
就像 QuickCheck 的Arbitrary
.
现在Serial
很简单:一个Serial
类型a
有一种方法 ( 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)
在这些情况下,我们只是忽略Depth
参数然后枚举每种类型的“所有”可能值。我们甚至可以为某些类型自动执行此操作
instance (Enum a, Bounded a) => Serial a where series _ = [minBound .. maxBound]
这是一种非常简单的彻底测试属性的方法——从字面上测试每一个可能的输入!显然,至少存在两个主要缺陷:(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]
好多了。
那是什么coseries
?就像coarbitrary
在Arbitrary
QuickCheck 的类型类中一样,它让我们构建了一系列“小”函数。请注意,我们在输入类型上编写实例——结果类型在另一个Serial
参数中传递给我们(我在下面调用results
)。
instance Serial Bool where
coseries results d = [\cond -> if cond then r1 else r2 |
r1 <- results d
r2 <- results d]
这些需要更多的聪明才智来编写,我实际上会推荐你使用alts
我将在下面简要描述的方法。
那么我们如何制作一些Series
sPerson
呢?这部分很简单
instance Series Person where
series d = SnowWhite : take (d-1) (map Dwarf [1..7])
...
但是我们的coseries
函数需要生成从Person
s 到其他东西的所有可能的函数。这可以使用altsN
SmallCheck 提供的一系列函数来完成。这是一种写法
coseries results d = [\person ->
case person of
SnowWhite -> f 0
Dwarf n -> f n
| f <- alts1 results d ]
基本思想是从具有实例的值到 的实例altsN results
生成一个Series
of N
-ary 函数。所以我们用它来创建一个从 [0..7]到我们需要的任何值的函数,然后我们将我们的 s 映射到数字并传入它们。N
Serial
Serial
Results
Serial
Person
所以现在我们有了 的Serial
实例Person
,我们可以使用它来构建更复杂的嵌套Serial
实例。对于“实例”,如果FairyTale
是Person
s 的列表,我们可以使用Serial a => Serial [a]
实例旁边的Serial Person
实例轻松创建Serial FairyTale
:
instance Serial FairyTale where
series = map makeFairyTale . series
coseries results = map (makeFairyTale .) . coseries results
(每个函数生成的(makeFairyTale .)
组合,这有点令人困惑)makeFairyTale
coseries