20

我刚刚开始使用带有一堆 Haskell 代码的 QuickCheck。我已经落后于时代了,我知道。这个问题分为两部分:

首先,快速检查的一般最佳实践是什么?到目前为止,我已经收集了以下内容:

  • 将您的测试命名为 prop_* (烦人,因为其他所有内容都是驼峰式)
  • 测试导出的代码(如果你正在测试内部,你可能做错了)
  • 测试属性,而不是示例
    • 不要说X is out of range, Y is in range
    • 相反,说if x is out of range, normalize x ≠ x(或其他一些这样的属性)

但我仍在掌握其他最佳实践。特别:

  • 财产保存在哪里?
    • 同一个文件?
    • test/目录中?(如果是这样,那么你如何导入这些东西src/?)
    • 在?下的Properties/目录中src

最重要的是,我们倾向于如何测试类型类的属性?例如,考虑以下(简化的)类型类:

class Gen a where
    next :: a -> a
    prev :: a -> a

我想测试财产∀ x: prev (next x) == x。当然,这涉及为每个实例编写测试。为每个实例编写相同的属性很乏味,尤其是在测试更复杂的情况下。概括此类测试的标准方法是什么?

4

3 回答 3

20

为每个实例编写相同的属性很乏味

你不这样做。您为该类编写一次属性:

class Gen a where
    next :: a -> a
    prev :: a -> a

np_prop :: (Eq a, Gen a) => a -> Bool
np_prop a = prev (next a) == a

然后为了测试它,你转换为一个特定的类型:

quickCheck (np_prop :: Int -> Bool)
quickCheck (np_prop :: String -> Bool)

你的其他问题我无能为力。

于 2012-04-06T01:06:08.603 回答
11

我相信prop_约定来自 QC 附带一个脚本,该脚本运行所有prop_以测试开头的功能。所以没有真正的理由这样做,但它在视觉上确实很突出(所以函数的属性fooprop_foo)。

测试内部并没有错。有两种方法:

  • 将属性放在与内部相同的模块中。这使得模块更大,并且需要对项目的 QC 无条件依赖(除非您使用 CPP 黑客)。

  • 在非导出模块中有内部结构,实际导出的功能从另一个模块重新导出。然后,您可以将内部模块导入到定义 QC 属性的模块中,并且只有在使用 .cabal 文件中指定的标志时才会构建该模块(并且具有 QC 依赖项)。

如果您的项目很大,那么拥有单独的src/test/目录可能会很有用(尽管有区别可能会阻止您测试内部)。但是,如果您的项目不是那么大(并且无论如何都位于一个整体模块层次结构下),那么就没有必要像那样拆分它。

正如 Norman Ramsey 在他的回答中所说,对于类型类,您可以将属性定义为类型类并相应地使用。

于 2012-04-06T02:37:53.960 回答
4

尝试

{-# LANGUAGE GADTs, ScopedTypeVariables #-}
import Test.QuickCheck hiding (Gen)

class Gen a where
  next :: a -> a
  prev :: a -> a

np_prop :: SomeGen -> Bool
np_prop (SomeGen a) = prev (next a) == a

main :: IO ()
main = quickCheck np_prop

instance Gen Bool where
  next True = False
  next False = True
  prev True = False
  prev False = True

instance Gen Int where
  next = (+ 1)
  prev = subtract 1

data SomeGen where
  SomeGen :: (Show a, Eq a, Arbitrary a, Gen a) => a -> SomeGen

instance Show SomeGen where
  showsPrec p (SomeGen a) = showsPrec p a
  show (SomeGen a) = show a

instance Arbitrary SomeGen where
  arbitrary = do
    GenDict (Proxy :: Proxy a) <- arbitrary
    a :: a <- arbitrary
    return $ SomeGen a
  shrink (SomeGen a) =
    map SomeGen $ shrink a

data GenDict where
  GenDict :: (Show a, Eq a, Arbitrary a, Gen a) => Proxy a -> GenDict

instance Arbitrary GenDict where
  arbitrary =
    elements
    [ GenDict (Proxy :: Proxy Bool)
    , GenDict (Proxy :: Proxy Int)
    ]

data Proxy a = Proxy

类型类被具体化为一个存在量化的字典,在该字典上Arbitrary定义了一个实例。然后使用这个Arbitrary字典实例来定义Arbitrary存在量化值的实例。

另一个示例在https://github.com/sonyandy/var/blob/4e0b12c390eb503616d53281b0fd66c0e1d0594d/tests/properties.hs#L217中给出。

如果您愿意使用ConstraintKinds. 以下仅定义一次。

data Some c where
  Some :: (Show a, Arbitrary a, c a) => a -> Some c

instance Show (Some c) where
  showsPrec p (Some a) = showsPrec p a
  show (Some a) = show a

instance Arbitrary (Dict c) => Arbitrary (Some c) where
  arbitrary = do
    Dict (Proxy :: Proxy a) :: Dict c <- arbitrary
    a :: a <- arbitrary
    return $ Some a
  shrink (Some a) =
    map Some $ shrink a

data Dict c where
  Dict :: (Show a, Arbitrary a, c a) => Proxy a -> Dict c

data Proxy a = Proxy

class (c a, d a) => (c &&# d) a
instance (c a, d a) => (c &&# d) a

对于您要测试的每个类型类,都需要一个Arbitrary实例Dict

instance Arbitrary (Dict (Eq &&# Gen)) where
  arbitrary =
    elements
    [ Dict (Proxy :: Proxy Bool)
    , Dict (Proxy :: Proxy Int)
    ]

np_prop :: Some (Eq &&# Gen) -> Bool
np_prop (Some a) = prev (next a) == a
于 2013-05-28T14:03:45.517 回答