如何使用SmallCheck在Haskell?(How to use SmallCheck in H

2019-09-03 05:50发布

我试图用SmallCheck来测试一个Haskell程序,但我不知道如何使用该库来测试我自己的数据类型。 很显然,我需要使用Test.SmallCheck.Series 。 不过,我觉得它的文档极其混乱。 我感兴趣的两个菜谱式的解决方案和逻辑(一元?)结构简单易懂的解释。 这里有一些问题我有(所有相关的):

  • 如果我有一个数据类型的data Person = SnowWhite | Dwarf Integer data Person = SnowWhite | Dwarf Integer ,我怎么解释smallCheck的有效值是Dwarf 1Dwarf 7 (或SnowWhite )? 如果我有一个复杂的FairyTale数据结构和构造makeTale :: [Person] -> FairyTale ,我想smallCheck从使用构造人-S的名单让童话-S?

    我设法让quickCheck这样的工作没有得到我的手用的明智应用太脏Control.Monad.liftM像功能makeTale 。 我无法想出一个办法,以做到这一点smallCheck (请解释给我!)。

  • 什么类型的关系SerialSeries等?

  • (可选)是什么点coSeries ? 如何使用Positive从类型SmallCheck.Series

  • (可选)的背后是什么应该是什么元表达式的逻辑,和什么是只是一个普通的功能,在smallCheck的上下文中的任何澄清,将不胜感激。

如果没有任何前奏/教程使用smallCheck ,我会很感激的链接。 非常感谢你!

更新:我要补充一点,我发现最有用和最可读的文档smallCheck是这个文件(PDF) 。 我找不到答案我的问题上的第一个样子; 它更是一个有说服力的广告比教程。

更新2:我提出我的问题有关的怪异Identity ,在类型显示出来Test.SmallCheck.list等地的单独的问题 。

Answer 1:

注意:这个答案描述了预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 ? 像coarbitraryArbitrary快速检查的类型类,它让我们建立了一系列的“小”的功能。 请注意,我们写在输入类型的实例---结果类型交给我们另一个Serial参数(即我下面叫results )。

instance Serial Bool where
  coseries results d = [\cond -> if cond then r1 else r2 | 
                        r1 <- results d
                        r2 <- results d]

这些都需要多一点别出心裁地写,我会实际上是指您使用alts ,我将简要介绍如下方法。


那么怎样才能使一些SeriesPerson 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产生SeriesN从进制功能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产生,这是有点混乱)



Answer 2:

  • 如果我有一个数据类型的data Person = SnowWhite | Dwarf Integer data Person = SnowWhite | Dwarf Integer ,我怎么解释smallCheck的有效值是Dwarf 1Dwarf 7 (或SnowWhite )?

首先,你需要决定要为每一个深度值。 有没有一个正确的答案在这里,它取决于你想如何细粒度的搜索空间是。

这里提供了两个可能的选择:

  1. people d = SnowWhite : map Dwarf [1..7]不依赖于深度)
  2. 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
  • 什么类型的关系SerialSeries等?

Serial是一种类型的类; Series是一种类型。 你可以有多个Series相同类型的-它们对应于不同的方式来枚举类型的值。 但是,它可能是艰巨的指定应该如何生成的每个值。 该Serial类让我们指定一个很好的默认生成一个特定类型的值。

的定义Serial

class Monad m => Serial m a where
  series   :: Series m a

所以,它的作用是分配特定的Series ma以给定的组合ma

  • 什么是点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.
  • 背后是什么东西应该是元表达式的逻辑,什么任何澄清只是一个普通的功能,在smallCheck的背景下,将不胜感激。

当你写一个Serial实例(或任何Series的表达),您在工作Series m单子。

当你写测试,您返回简单功能的工作BoolProperty m



Answer 3:

虽然我认为,@电话的回答是一个很好的解释(我希望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]))


文章来源: How to use SmallCheck in Haskell?