Testing Haskell QuickCheck最佳实践(尤其是在测试类型类时)
我刚刚开始使用QuickCheck和一堆Haskell代码。我知道,我落后于时代。这个问题分为两部分: 首先,快速检查的一般最佳实践是什么?到目前为止,我已经了解了以下内容:Testing Haskell QuickCheck最佳实践(尤其是在测试类型类时),testing,haskell,Testing,Haskell,我刚刚开始使用QuickCheck和一堆Haskell代码。我知道,我落后于时代。这个问题分为两部分: 首先,快速检查的一般最佳实践是什么?到目前为止,我已经了解了以下内容: 将您的测试命名为prop_*(恼人,因为其他一切都是如此) 测试导出的代码(如果您正在测试内部代码,那么很可能是做错了) 测试属性,而不是示例 不要说X超出范围,Y在范围内 相反,如果x超出范围,则说,将x规格化≠ x(或其他类似属性) 但我仍在掌握其他最佳实践。特别是: 财产存放在哪里? 同一个文件 在tes
- 将您的测试命名为prop_*(恼人,因为其他一切都是如此)
- 测试导出的代码(如果您正在测试内部代码,那么很可能是做错了)
- 测试属性,而不是示例
- 不要说X超出范围,Y在范围内
- 相反,如果x超出范围,则说
(或其他类似属性),将x规格化≠ x
- 财产存放在哪里?
- 同一个文件
- 在
目录中?(如果是这样,那么如何在test/
中导入内容?)src/
- 在
下的src
目录中Properties/
class Gen a where
next :: a -> a
prev :: a -> a
我想测试属性∀ x:prev(next x)=x
。当然,这涉及到为每个实例编写测试。为每个实例编写相同的属性是很乏味的,尤其是当测试更复杂时。推广此类测试的标准方法是什么
为每个实例编写相同的属性是乏味的
你不能这样做。为类编写一次属性:
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)
你的其他问题我帮不上忙。我相信
prop\u
惯例来自QC,它附带了一个脚本,可以运行以prop\u
作为测试开始的所有函数。因此没有真正的理由这么做,但它确实在视觉上很突出(因此函数foo
的属性是prop\u foo
)
测试内部构件并没有什么问题。有两种方法可以做到这一点:
- 将属性与内部构件放在同一模块中。这使得模块更大,并且要求项目无条件地依赖于QC(除非您使用CPP hackery)
- 在非导出模块中具有内部构件,实际要导出的函数从另一个模块重新导出。然后,您可以将内部模块导入到定义QC属性的模块中,并且该模块仅在使用.cabal文件中指定的标志时生成(并且具有QC依赖项)
src/
和test/
目录可能会很有用(尽管有区别可能会阻止您测试内部构件)。但是,如果您的项目没有那么大(并且始终位于一个整体模块层次结构下),那么就没有必要像那样将其拆分
正如Norman Ramsey在他的回答中所说,对于类型类,您可以将属性定义为在类型类上,并相应地使用它。试试看
{-# 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
对于要测试的每个类型类,都需要一个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
对于使用并行
src/
和test/
目录时的导入,您需要在.cabal
文件中设置Hs-Source-Dirs:src,test
,以便两个目录都位于模块搜索路径中。为什么内部不能有属性?它们当然可以,只是很难对它们进行测试(根据我的经验)测试导出的行为比测试实现细节有用得多。我们经常做的事情是有一个Cabal测试套件
部分,它直接依赖于内部模块(而不是在同一个.Cabal文件中定义的库),这样您就可以测试这些模块,而需要花费一些额外的编译时间。
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