我试图用SmallCheck来测试一个Haskell程序,但我不知道如何使用该库来测试我自己的数据类型。 很显然,我需要使用Test.SmallCheck.Series 。 不过,我觉得它的文档极其混乱。 我感兴趣的两个菜谱式的解决方案和逻辑(一元?)结构简单易懂的解释。 这里有一些问题我有(所有相关的):
如果我有一个数据类型的data Person = SnowWhite | Dwarf Integer
data Person = SnowWhite | Dwarf Integer
,我怎么解释smallCheck
的有效值是Dwarf 1
至Dwarf 7
(或SnowWhite
)? 如果我有一个复杂的FairyTale
数据结构和构造makeTale :: [Person] -> FairyTale
,我想smallCheck
从使用构造人-S的名单让童话-S?
我设法让quickCheck
这样的工作没有得到我的手用的明智应用太脏Control.Monad.liftM
像功能makeTale
。 我无法想出一个办法,以做到这一点smallCheck
(请解释给我!)。
什么类型的关系Serial
, Series
等?
(可选)是什么点coSeries
? 如何使用Positive
从类型SmallCheck.Series
?
(可选)的背后是什么应该是什么元表达式的逻辑,和什么是只是一个普通的功能,在smallCheck的上下文中的任何澄清,将不胜感激。
如果没有任何前奏/教程使用smallCheck
,我会很感激的链接。 非常感谢你!
更新:我要补充一点,我发现最有用和最可读的文档smallCheck
是这个文件(PDF) 。 我找不到答案我的问题上的第一个样子; 它更是一个有说服力的广告比教程。
更新2:我提出我的问题有关的怪异Identity
,在类型显示出来Test.SmallCheck.list
等地的单独的问题 。
注意:这个答案描述了预1.0版本SmallCheck的。 请参见这篇博客的SmallCheck 0.6和1.0之间的重要区别。
SmallCheck就像是快速检查的,因为它在可能的类型空间的某些部分测试的属性。 所不同的是,它试图详尽地列举了一系列所有的“小”的值,而不是短小值的任意子集。
正如我暗示,SmallCheck的Serial
就像是快速检查的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
快速检查的类型类,它让我们建立了一系列的“小”的功能。 请注意,我们写在输入类型的实例---结果类型交给我们另一个Serial
参数(即我下面叫results
)。
instance Serial Bool where
coseries results d = [\cond -> if cond then r1 else r2 |
r1 <- results d
r2 <- results d]
这些都需要多一点别出心裁地写,我会实际上是指您使用alts
,我将简要介绍如下方法。
那么怎样才能使一些Series
的Person
S' 这部分是容易
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
的N
从进制功能N
值与Serial
实例的Serial
的实例Results
。 所以我们用它来创建[0..7]的功能,先前定义的Serial
价值,无论我们所需要的,那么我们映射我们的Person
s到号码,并通过“时间英寸
所以,现在我们有一个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
产生,这是有点混乱)
- 如果我有一个数据类型的
data Person = SnowWhite | Dwarf Integer
data Person = SnowWhite | Dwarf Integer
,我怎么解释smallCheck
的有效值是Dwarf 1
至Dwarf 7
(或SnowWhite
)?
首先,你需要决定要为每一个深度值。 有没有一个正确的答案在这里,它取决于你想如何细粒度的搜索空间是。
这里提供了两个可能的选择:
-
people d = SnowWhite : map Dwarf [1..7]
不依赖于深度) -
people d = take d $ SnowWhite : map Dwarf [1..7]
深度的每个单元由一个元件提高了的搜索空间)
你对决定后,你的Serial
实例就是这么简单
instance Serial m Person where
series = generate people
我们离开m
多态这里我们不要求底层的单子的任何具体结构。
- 如果我有一个复杂的
FairyTale
数据结构和构造makeTale :: [Person] -> FairyTale
,我想smallCheck
从使用构造人-S的名单让童话-S?
使用cons1
:
instance Serial m FairyTale where
series = cons1 makeTale
Serial
是一种类型的类; Series
是一种类型。 你可以有多个Series
相同类型的-它们对应于不同的方式来枚举类型的值。 但是,它可能是艰巨的指定应该如何生成的每个值。 该Serial
类让我们指定一个很好的默认生成一个特定类型的值。
的定义Serial
是
class Monad m => Serial m a where
series :: Series m a
所以,它的作用是分配特定的Series ma
以给定的组合m
和a
。
这是需要以产生功能性类型的值。
- 如何使用
Positive
从类型SmallCheck.Series
?
例如,像这样:
> smallCheck 10 $ \n -> n^3 >= (n :: Integer)
Failed test no. 5.
there exists -2 such that
condition is false
> smallCheck 10 $ \(Positive n) -> n^3 >= (n :: Integer)
Completed 10 tests without failure.
- 背后是什么东西应该是元表达式的逻辑,什么任何澄清只是一个普通的功能,在smallCheck的背景下,将不胜感激。
当你写一个Serial
实例(或任何Series
的表达),您在工作Series m
单子。
当你写测试,您返回简单功能的工作Bool
或Property m
。
虽然我认为,@电话的回答是一个很好的解释(我希望smallCheck
实际工作他所描述的方式),他为我不工作的代码(与smallCheck
第1版)。 我设法得到以下工作...
UPDATE /警告:下面的代码是错误的一个相当微妙的原因。 对于修改后的版本,和详细信息,请参阅此答案下面提及的问题。 短的版本是,代替instance Serial Identity Person
必须写instance (Monad m) => Series m Person
。
...但我发现使用Control.Monad.Identity
和所有的编译器标志怪异,我问一个单独的问题有关。
还要注意的是,虽然Series Person
(或实际Series Identity Person
)实际上不是完全一样的功能Depth -> [Person]
(见@电话的答案),该函数generate :: Depth -> [a] -> Series ma
转换它们之间。
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, FlexibleContexts, UndecidableInstances #-}
import Test.SmallCheck
import Test.SmallCheck.Series
import Control.Monad.Identity
data Person = SnowWhite | Dwarf Int
instance Serial Identity Person where
series = generate (\d -> SnowWhite : take (d-1) (map Dwarf [1..7]))