Quickcheck中的测试不相同?

时间:2013-02-17 08:15:52

标签: haskell quickcheck

我是QuickCheck的新手,无法完全理解如何使用它。

假设我意外地使用Set(而不是List)实施了数据类型:

data Profile = Profile (Set Strategy)
--for completeness:
data Strategy = Strategy Int

然后遇到这个bug,两个对象相等,即使它们不应该:

Profile (Set.fromList [1,2,3]) == Profile (Set.fromList [2,1,3])
-- D'OH! Order doesn't matter in sets!

如何编写QuickCheck测试用例来测试此案例? 在伪代码中,这看起来像这样:

assertNotEqual(Profile (Set.fromList [1,2,3]), Profile (Set.fromList [2,1,3]))
assertEqual(Profile (Set.empty), Profile (Set.empty ))

我已经尝试在项目的github上查看examples,但似乎它们不包括这些琐碎的案例。

欢迎任何提示!

3 个答案:

答案 0 :(得分:3)

您可以使用SmallCheck

支持的存在量化来实现
> depthCheck 5 $ exists $ \xs -> (xs :: [Integer]) /= sort xs
    Depth 5:
      Completed 1 test(s) without failure.
> depthCheck 5 $ exists $ \xs -> fromList (xs :: [Integer]) /= fromList (sort xs)
    Depth 5:
      Failed test no. 1. Test values follow.
      non-existence

使用通用量化的另一种选择(也适用于QuickCheck):

> smallCheck 5 $ \xs ys -> xs /= ys ==> fromList (xs :: [Integer]) /= fromList ys
Depth 0:
  Completed 1 test(s) without failure.
  But 1 did not meet ==> condition.
Depth 1:
  Completed 4 test(s) without failure.
  But 2 did not meet ==> condition.
Depth 2:
  Failed test no. 26. Test values follow.
  [0]
  [0,0]

答案 1 :(得分:3)

  

如何编写QuickCheck测试用例来测试此案例?

你不应该! QuickCheck是基于属性的测试的工具。在基于属性的测试中,您给出了数据结构(或其他)的属性,测试工具将自动生成测试用例,以查看该属性是否适用于生成的测试用例。那么,让我们看看你如何给出一个属性,而不是像[1,2,3]那样给出具体的测试用例,以及为什么属性是有利的!

因此。我从

开始
import Test.QuickCheck
import qualified Data.Set as Set 
import Data.Set (Set)

data Profile = Profile (Set Int)
  deriving (Eq, Show)

mkProfile :: [Int] -> Profile
mkProfile = Profile . Set.fromList

-- | We will test if the order of the arguments matter.
test_mkProfile :: [Int] -> Bool
test_mkProfile xs = (mkProfile xs `comp` mkProfile (reverse xs))
  where comp | length xs <= 1 = (==)
             | otherwise      = (/=)

这就是我对我的属性进行推理的原因:嗯,对于空和单例列表情况,reverse只是标识,因此我们希望mkProfile xsmkProfile (reverse xs)相同。对?我的意思是mkProfile获得完全相同的论点。在length xs >= 2然后reverse xs显然不是xs的情况下。像reverse [1, 2] /= [2, 1]一样。我们知道Profile 关心订单。

现在让我们在ghci

中尝试一下
*Main> quickCheck test_mkProfile 
*** Failed! Falsifiable (after 3 tests and 1 shrink):     
[0,0]

现在请注意,我们的代码中确实存在两个错误。一,首先,Profile应该使用列表而不是集合。第二,我们的财产是错的!因为即使length xs >= 2xs == reverse (xs)也可以。让我们尝试修复第一个错误,看看quickcheck如何指出第二个缺陷。

data Profile2 = Profile2 [Int]
  deriving (Eq, Show)

mkProfile2 :: [Int] -> Profile2
mkProfile2 = Profile2 

-- | We will test if the order of the arguments matter.
test_mkProfile2 :: [Int] -> Bool
test_mkProfile2 xs = (mkProfile2 xs `comp` mkProfile2 (reverse xs))
  where comp | length xs <= 1 = (==)
             | otherwise      = (/=)

请记住,我们的代码现在正确但我们的财产存在缺陷!

*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
*** Failed! Falsifiable (after 8 tests):                   
[-8,-8]

是。你还需要思考!或者你可能会因为你的代码完全通过700个测试用例而得到一切都没问题的错误印象!好了,现在我们来修理我们的房产吧!

test_mkProfile2_again :: [Int] -> Bool
test_mkProfile2_again xs = (mkProfile2 xs `comp` mkProfile2 ys)
  where ys   = reverse xs
        comp | xs == ys  = (==)
             | otherwise = (/=)

现在让我们看看它多次运作了!

*Main> import Control.Monad
*Main Control.Monad> forever $ quickCheck test_mkProfile2_again
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
... (a lot of times)

万岁。我们现在不仅在我们的Profile实现中压缩了这个错误,而且还更好地理解了我们的代码及其所遵循的属性!

答案 2 :(得分:2)

正如我评论的那样,回答这个问题的主要问题是Profile类型缺乏结构。如果您定义Profile,一组操作和不变量,则可以轻松进行快速检查。

例如,假设您有一个配置文件,一种构建配置文件的方法,以及一种修改配置文件的方法。这些属性都是唯一性

 module Profile (Profile, mkProfile, addItem) where
 import Data.Set

 newtype Profile = Profile { unProfile :: Set Int }
   deriving (Eq, Ord, Show)

 mkProfile :: [Int] -> Profile
 mkProfile = Profile . fromList

 addItem :: Int -> Profile -> Profile
 addItem x = Profile . insert x . unProfile

您可以通过在每次操作之前和之后声明属性来快速检查这样的ADT:

 import Test.QuickCheck
 import Profile as P

 prop_unique_list_unique_profile :: [Int] -> [Int] -> Bool
 prop_unique_list_unique_profile xs ys =
    xs /= ys ==> mkProfile xs /= mkProfile ys

 prop_addItem_nonequal :: Int -> [Int] -> Bool
 prop_addItem_nonequal x xs = P.addItem x xs /= xs