我试图使用SmallCheck来测试Haskell程序,但是我不明白如何使用这个库来测试我自己的数据类型。显然,我需要使用Test.SmallCheck.Series。然而,我发现它的文档非常令人困惑。我对食谱式的解决方案和逻辑解释(一元?)都感兴趣。结构。以下是我的一些问题(所有相关问题):
data Person = SnowWhite | Dwarf Integer,如何向smallCheck解释有效值是Dwarf 1通过Dwarf 7 (或SnowWhite)?如果我有一个复杂的FairyTale数据结构和一个构造函数makeTale :: [Person] -> FairyTale,并且希望smallCheck使用构造函数从人的列表中创建童话-s,该怎么办?
我成功地使quickCheck像这样工作,而不让我的手太脏,因为我将Control.Monad.liftM的明智应用程序应用于makeTale这样的函数。我想不出用smallCheck做这件事的方法(请给我解释一下!)Serial、Series等类型之间的关系是什么?coSeries的意义是什么?如何使用来自Positive的SmallCheck.Series类型如果有任何介绍/教程使用smallCheck,我会感谢一个链接。非常感谢!
更新:--我应该补充说,我为smallCheck找到的最有用和最易读的文档是本文(PDF)。我第一眼就找不到问题的答案,这与其说是一本教程,不如说是一份有说服力的广告。
更新2:,我把关于在Test.SmallCheck.list和其他地方出现的奇怪的Identity的问题转移到了单独问题上。
发布于 2013-05-15 02:58:30
注意:这个答案描述了SmallCheck的1.0前版本。有关这篇博客文章 0.6和1.0之间的重要差异,请参见SmallCheck。
SmallCheck与QuickCheck类似,因为它在可能的类型空间的某个部分测试一个属性。不同之处在于,它试图穷尽地列举一系列“小”值,而不是小值的任意子集。
正如我所暗示的,SmallCheck的Serial就像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在QuickCheck的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 of Persons呢?这部分很容易
instance Series Person where
series d = SnowWhite : take (d-1) (map Dwarf [1..7])
...但是我们的coseries函数需要生成从Person到其他东西的每一个可能的函数。这可以使用altsN系列SmallCheck提供的函数来完成。这里有一种写它的方法
coseries results d = [\person ->
case person of
SnowWhite -> f 0
Dwarf n -> f n
| f <- alts1 results d ]其基本思想是altsN results从带有Serial实例的N值到Results的Serial实例生成N-ary函数。因此,我们使用它从前面定义的Serial值0.7创建一个函数到我们需要的任何东西,然后我们将Person映射到数字并传递给它们。
现在我们有了一个用于Serial的Person实例,我们可以使用它来构建更复杂的嵌套Serial实例。对于“例如”,如果FairyTale是Person的列表,我们可以在Serial Person实例旁边使用Serial a => Serial [a]实例来轻松地创建Serial FairyTale
instance Serial FairyTale where
series = map makeFairyTale . series
coseries results = map (makeFairyTale .) . coseries results( (makeFairyTale .)将makeFairyTale与coseries生成的每个函数组合在一起,这有点让人费解)
发布于 2013-07-01 22:55:24
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多态,因为我们不需要底层monad的任何特定结构。
FairyTale数据结构和一个构造函数makeTale :: [Person] -> FairyTale,并且希望smallCheck使用构造函数从人的列表中创建童话-s,该怎么办?使用cons1
instance Serial m FairyTale where
series = cons1 makeTaleSerial、Series等类型之间的关系是什么?Serial是一个类型类;Series是一个类型。您可以拥有相同类型的多个Series --它们对应于枚举该类型值的不同方法。然而,为每个值指定应该如何生成它可能是很困难的。Serial类允许我们为生成特定类型的值指定一个很好的缺省值。
Serial的定义是
class Monad m => Serial m a where
series :: Series m a因此,它所做的就是将一个特定的Series m a分配给给定的m和a组合。
coseries的意义是什么?它需要生成函数类型的值。
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.在编写Serial实例(或任何Series表达式)时,您在Series m monad中工作。
在编写测试时,使用返回Bool或Property m的简单函数。
发布于 2013-05-15 05:19:43
虽然我认为@tel的答案是一个很好的解释(我希望smallCheck能够像他描述的那样工作),但是他提供的代码对我不起作用( smallCheck版本1)。我设法让下面的人开始工作..。
更新/警告:,下面的代码是错误的,原因很微妙。有关更正的版本和详细信息,请参见下面提到的问题的这个答案。简短的版本是,不是
instance Serial Identity Person,而是必须编写instance (Monad m) => Series m Person。
..。但是我发现Control.Monad.Identity和所有编译器标志的使用都很奇怪,我已经询问了单独问题。
还请注意,虽然Series Person (或实际上是Series Identity Person)实际上与函数Depth -> [Person]不完全相同(请参阅@tel的答案),但函数generate :: Depth -> [a] -> Series m a在它们之间进行转换。
{-# 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]))https://stackoverflow.com/questions/16555291
复制相似问题