这是一个简单的功能.它接受输入Int并返回(Int,Int)对的(可能为空)列表,其中输入Int是任何对的立方元素的总和.
cubeDecomposition :: Int -> [(Int, Int)]
cubeDecomposition n = [(x, y) | x <- [1..m], y <- [x..m], x^3 + y^3 == n]
where m = truncate $fromIntegral n ** (1/3)
-- cubeDecomposition 1729
-- [(1,12),(9,10)]
我想测试以上属性的属性;如果我将每个元素立方体并将任何返回元组相加,那么我会得到我的输入:
import Control.Arrow
cubedElementsSumToN :: Int -> Bool
cubedElementsSumToN n = all (== n) d
where d = map (uncurry (+) . ((^3) *** (^3))) (cubeDecomposition n)
出于运行时考虑,我想在使用QuickCheck进行测试时将输入Ints限制为一定的大小.我可以定义一个合适的类型和任意实例:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Test.QuickCheck
newtype SmallInt = SmallInt Int
deriving (Show, Eq, Enum, Ord, Num, Real, Integral)
instance Arbitrary SmallInt where
arbitrary = fmap SmallInt (choose (-10000000, 10000000))
然后我想我必须定义使用SmallInt而不是Int的函数和属性的版本:
cubeDecompositionQC :: SmallInt -> [(SmallInt, SmallInt)]
cubeDecompositionQC n = [(x, y) | x <- [1..m], y <- [x..m], x^3 + y^3 == n]
where m = truncate $fromIntegral n ** (1/3)
cubedElementsSumToN' :: SmallInt -> Bool
cubedElementsSumToN' n = all (== n) d
where d = map (uncurry (+) . ((^3) *** (^3))) (cubeDecompositionQC n)
-- cubeDecompositionQC 1729
-- [(SmallInt 1,SmallInt 12),(SmallInt 9,SmallInt 10)]
这工作正常,标准的100个测试按预期通过.但是,当我真正需要的是自定义生成器时,似乎没有必要定义新的类型,实例和函数.所以我尝试了这个:
smallInts :: Gen Int
smallInts = choose (-10000000, 10000000)
cubedElementsSumToN'' :: Int -> Property
cubedElementsSumToN'' n = forAll smallInts $\m -> all (== n) (d m)
where d = map (uncurry (+) . ((^3) *** (^3)))
. cubeDecomposition
现在,我最初几次运行它,一切正常,所有测试都通过了.但在随后的运行中我发现了失败.可靠地提高测试尺寸可以找到一个:
*** Failed! Falsifiable (after 674 tests and 1 shrink):
0
8205379
我在这里有点困惑,因为QuickCheck返回了两个缩小的输入 – 0和8205379,在那里我会直观地期待一个.此外,这些输入按预期工作(至少在我的展示财产上):
*Main> cubedElementsSumToN 0
True
*Main> cubedElementsSumToN 8205379
True
因此,似乎显然在属性中存在使用我定义的自定义Gen的问题.
我做错了什么?
最佳答案 我很快意识到我写的这个属性显然是不正确的.这是使用原始cubedElementsSumToN属性执行此操作的正确方法:
quickCheck (forAll smallInts cubedElementsSumToN)
这读起来非常自然.