我如何编写一个 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 = (/=)
这就是我对我的属性的推理:嗯,对于空列表和单例列表的情况,thenreverse
只是身份,所以我们期望mkProfile xs
与mkProfile (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 >= 2
,xs == 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
实现中的错误,而且对我们的代码及其所遵循的属性也有了更好的理解!