Testing Case Study: Specifying a Pretty Printer
“(<>)” 是连接两个Doc 的函数,加上fold 就变成连接多个Doc 的函数
Generating Test Data
data Doc = Empty
| Char Char
| Text String
| Line
| Concat Doc Doc
| Union Doc Doc
deriving (Show,Eq)
QuickCheck 提供了一个名为Arbitrary 的typeclass 。class 是定义typeclass 的关键字。定义一个typeclass 就是定义一组泛型函数。
QuickCheck 库中arbitrary 函数的定义
class Arbitrary a where
arbitrary :: Gen a
这里的Gen 是一个Monad 。
QuickCheck 库中三个函数的定义
elements :: [a] -> Gen a
choose :: Random a => (a, a) -> Gen a
oneof :: [Gen a] -> Gen a
为简单的数据类型编写生成器
data Ternary
= Yes
| No
| Unknown
deriving (Eq,Show)
Ternary 是一个ternary logic 。
我们可以为Ternary 实现一个Arbitrary 的实例
-- 方法一
instance Arbitrary Ternary where
arbitrary = elements [Yes, No, Unknown]
-- 方法二
instance Arbitrary Ternary where
arbitrary = do
n <- choose (0, 2) :: Gen Int
return $ case n of
0 -> Yes
1 -> No
_ -> Unknown
instance (Arbitrary a, Arbitrary b) => Arbitrary (a, b) where
arbitrary = do
x <- arbitrary
y <- arbitrary
return (x, y)
instance Arbitrary Char where
arbitrary = elements (['A'..'Z'] ++ ['a' .. 'z'] ++ " ~!@#$%^&*()")
生成任意类型的随机数据
-- 方法一
instance Arbitrary Doc where
arbitrary = do
n <- choose (1,6) :: Gen Int
case n of
1 -> return Empty
2 -> do x <- arbitrary
return (Char x)
3 -> do x <- arbitrary
return (Text x)
4 -> return Line
5 -> do x <- arbitrary
y <- arbitrary
return (Concat x y)
6 -> do x <- arbitrary
y <- arbitrary
return (Union x y)
-- 方法二
instance Arbitrary Doc where
arbitrary =
oneof [ return Empty
, liftM Char arbitrary
, liftM Text arbitrary
, return Line
, liftM2 Concat arbitrary arbitrary
, liftM2 Union arbitrary arbitrary ]
生成随机DOC
docs <- sample' (arbitrary::Gen [Doc])
print docs
-- >[[],[],[Char 's',Text "Om/b",Char 'w'],[Empty,Empty,Text "/149", .. =/"/188/150y"]]
Testing Document Construction
Doc 的两个基础函数
-- 生成空文档
empty :: Doc
empty = Empty
-- 合并两个文档
(<>) :: Doc -> Doc -> Doc
Empty <> y = y
x <> Empty = x
x <> y = x `Concat` y
其它基础函数
char :: Char -> Doc
char c = Char c
text :: String -> Doc
text "" = Empty
text s = Text s
double :: Double -> Doc
double d = text (show d)
line :: Doc
line = Line
验证一个文档与空文档合并什么也不会发生
prop_empty_id x =
empty <> x == x
&&
x <> empty == x
-- quickCheck prop_empty_id -- >+++ OK, passed 100 tests.
-- verboseCheck prop_empty_id
其它基础测试
prop_char c = char c == Char c
prop_text s = text s == if null s then Empty else Text s
prop_line = line == Line
prop_double d = double d == text (show d)
Using Lists as a Model
fold :: (Doc -> Doc -> Doc) -> [Doc] -> Doc
fold f = foldr f empty
hcat :: [Doc] -> Doc
hcat = fold (<>) -- “(<>)” 是连接两个Doc 的函数,加上fold 就变成连接多个Doc 的函数
prop_hcat xs = hcat xs == glue xs
where
glue [] = empty
glue (d:ds) = d <> glue ds
punctuate :: Doc -> [Doc] -> [Doc]
punctuate p [d] = [d]
punctuate p (d:ds) = (d <> p) : punctuate p ds
punctuate p _ = []
prop_punctuate s xs = punctuate s xs == intersperse s xs
-- quickCheck prop_punctuate -- 测试失败!*** Failed! Falsifiable (after 4 tests and 1 shrink): Text "~"
-- 解决办法
prop_punctuate' s xs = punctuate s xs == combine (intersperse s xs)
where
combine [] = []
combine [x] = [x]
combine (x:Empty:ys) = x : combine ys
combine (Empty:y:ys) = y : combine ys
combine (x:y:ys) = x `Concat` y : combine ys
Putting It All Together
这一小节和下一节略。因为Test.QuickCheck.Batch 在新版本的QuickCheck 库不存了,待以后补充。
更多信息搜索stackoverflow:
http://stackoverflow.com/search?page=1&tab=relevance&q=quickcheck
Measuring Test Coverage with HPC
略