如何在 QuickCheck 中 运行 指定数量的测试?
How to run a specific number of tests in QuickCheck?
我正在努力学习 Haskell,特别是 QuickCheck。虽然 Haskell 在网上有很多信息,但我正在努力使用 QuickCheck 创建一些随机测试。
例如我有以下脚本:
import Test.QuickCheck
whatAge :: Int -> Int -> Int -> Int -> Bool
whatAge age1 age2 age3 age4
| age1 + age2 + age3 + age4 == 5 = True
| otherwise = False
main = do
verboseCheck whatAge
当我 运行 它显示:
*** Failed! Falsifiable (after 1 test):
0
0
0
0
相当不错,它显示了一个函数为假的测试。
不过我想做的是:
- 即使在失败时也生成 200 个随机测试(a.k.a 即使 whatAge 函数的输出为假)
可以在我的函数参数上设置一个范围,例如:
x1 range from 1 to 30
x2 range from 1 to 40
x3 range from 1 to 50
x4 range from 1 to 60
能够生成非重复测试
根据我的理解,nr 3 对于 QuickCheck 来说是不可能的,为此我将不得不使用 smallCheck,但我不确定第 1 点和第 2 点。
对于输入的简单属性,您可以使用适当的 Arbitrary
实例创建一个新类型来捕获它们。所以:
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
import Data.Proxy
import GHC.TypeLits
import Test.QuickCheck
newtype Range (m :: Nat) (n :: Nat) a = Range { getVal :: a }
deriving (Eq, Ord, Read, Show, Num, Real, Enum, Integral)
numVal :: forall n a. (KnownNat n, Num a) => a
numVal = fromInteger (natVal @n Proxy)
instance (KnownNat m, KnownNat n, Arbitrary a, Integral a) => Arbitrary (Range m n a) where
arbitrary = fromInteger <$> choose (numVal @m, numVal @n)
shrink hi = go (numVal @m) where
go lo | lo == hi = [] | otherwise = lo : go ((lo+hi+1)`div`2) -- overflow? what's that? lmao
whatAge :: Range 1 30 Int -> Range 1 40 Int -> Range 1 50 Int -> Range 1 60 Int -> Bool
whatAge (Range age1) (Range age2) (Range age3) (Range age4)
= age1 + age2 + age3 + age4 == 5
在 ghci 中:
> verboseCheck whatAge
Failed:
Range {getVal = 17}
Range {getVal = 29}
Range {getVal = 3}
Range {getVal = 16}
Failed:
Range {getVal = 1}
Range {getVal = 29}
Range {getVal = 3}
Range {getVal = 16}
Failed:
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 3}
Range {getVal = 16}
Failed:
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 16}
Failed:
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
*** Failed! Falsifiable (after 1 test and 4 shrinks):
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
对于更复杂的属性,如果不清楚如何直接创建一个满足属性的随机值,您可以使用QuickCheck的(==>)
运算符。例如,对于上述范围检查:
> verboseCheck (\x -> (1 <= x && x <= 30) ==> x*2 < 60)
Skipped (precondition false):
0
Passed:
1
*** Failed! Falsifiable (after 33 tests):
30
要进行恰好 200 次测试,您可以调用 quickCheckWith
进行一次测试,200 次;或者您可以通过在 arbitrary
上手动调用您的 属性 来直接 generate
测试结果。
我正在努力学习 Haskell,特别是 QuickCheck。虽然 Haskell 在网上有很多信息,但我正在努力使用 QuickCheck 创建一些随机测试。
例如我有以下脚本:
import Test.QuickCheck
whatAge :: Int -> Int -> Int -> Int -> Bool
whatAge age1 age2 age3 age4
| age1 + age2 + age3 + age4 == 5 = True
| otherwise = False
main = do
verboseCheck whatAge
当我 运行 它显示:
*** Failed! Falsifiable (after 1 test):
0
0
0
0
相当不错,它显示了一个函数为假的测试。
不过我想做的是:
- 即使在失败时也生成 200 个随机测试(a.k.a 即使 whatAge 函数的输出为假)
可以在我的函数参数上设置一个范围,例如:
x1 range from 1 to 30 x2 range from 1 to 40 x3 range from 1 to 50 x4 range from 1 to 60
能够生成非重复测试
根据我的理解,nr 3 对于 QuickCheck 来说是不可能的,为此我将不得不使用 smallCheck,但我不确定第 1 点和第 2 点。
对于输入的简单属性,您可以使用适当的 Arbitrary
实例创建一个新类型来捕获它们。所以:
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
import Data.Proxy
import GHC.TypeLits
import Test.QuickCheck
newtype Range (m :: Nat) (n :: Nat) a = Range { getVal :: a }
deriving (Eq, Ord, Read, Show, Num, Real, Enum, Integral)
numVal :: forall n a. (KnownNat n, Num a) => a
numVal = fromInteger (natVal @n Proxy)
instance (KnownNat m, KnownNat n, Arbitrary a, Integral a) => Arbitrary (Range m n a) where
arbitrary = fromInteger <$> choose (numVal @m, numVal @n)
shrink hi = go (numVal @m) where
go lo | lo == hi = [] | otherwise = lo : go ((lo+hi+1)`div`2) -- overflow? what's that? lmao
whatAge :: Range 1 30 Int -> Range 1 40 Int -> Range 1 50 Int -> Range 1 60 Int -> Bool
whatAge (Range age1) (Range age2) (Range age3) (Range age4)
= age1 + age2 + age3 + age4 == 5
在 ghci 中:
> verboseCheck whatAge
Failed:
Range {getVal = 17}
Range {getVal = 29}
Range {getVal = 3}
Range {getVal = 16}
Failed:
Range {getVal = 1}
Range {getVal = 29}
Range {getVal = 3}
Range {getVal = 16}
Failed:
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 3}
Range {getVal = 16}
Failed:
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 16}
Failed:
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
*** Failed! Falsifiable (after 1 test and 4 shrinks):
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
Range {getVal = 1}
对于更复杂的属性,如果不清楚如何直接创建一个满足属性的随机值,您可以使用QuickCheck的(==>)
运算符。例如,对于上述范围检查:
> verboseCheck (\x -> (1 <= x && x <= 30) ==> x*2 < 60)
Skipped (precondition false):
0
Passed:
1
*** Failed! Falsifiable (after 33 tests):
30
要进行恰好 200 次测试,您可以调用 quickCheckWith
进行一次测试,200 次;或者您可以通过在 arbitrary
上手动调用您的 属性 来直接 generate
测试结果。