【问题标题】:Haskell QuickCheck best practices (especially when testing type classes)Haskell QuickCheck 最佳实践(尤其是在测试类型类时)
【发布时间】:2012-04-06 00:45:18
【问题描述】:

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

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

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

但我仍在掌握其他最佳做法。特别是:

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

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

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

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

【问题讨论】:

  • 对于使用并行src/test/ 目录时的导入,您需要在.cabal 文件中设置Hs-Source-Dirs: src, test,以便这两个目录都在模块搜索路径中。跨度>
  • 为什么内部不能有属性?
  • 他们当然可以,只是更难对他们进行测试,而且(根据我的经验)测试导出的行为而不是实现细节更有用。

标签: testing haskell


【解决方案1】:

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

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

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)

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

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

    【讨论】:

    • 我们经常做的是有一个 Cabal test-suite 部分,它直接依赖于内部模块(而不是在同一个 .cabal 文件中定义的库),这样你就可以在一些额外编译时间的成本。
    【解决方案3】:

    试试

    {-# 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
    

    对于您要测试的每个类型类,都需要 ArbitraryDict 实例。

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-06
      • 1970-01-01
      • 2018-04-23
      • 2012-11-01
      • 1970-01-01
      • 2011-06-29
      • 2020-02-05
      • 2011-08-16
      相关资源
      最近更新 更多