请考虑以下代码:
-- Represents a parsing result of an ANSII coded string.
data Slice = Slice
{ text :: String,
color :: Color
}
newtype Color = Color
{ string :: String
}
-- A function that receives a string with ANSII esacpe codes and returns a list of slices.
categorize:: String -> [Slice]
categorize codedString = ...
现在,我想为
categorize
函数编写一个快速检查属性。
我有这样的想法:
-- A quickcheck generator for ANSII coded strings.
ansiEscapeStrings :: Gen String
ansiEscapeStrings = ...
main =
verboseCheck $
forAll
ansiEscapeStrings
(\codedString -> categorize codedString == WHAT_GOES_HERE)
我的问题是什么代替
WHAT_GOES_HERE
?
提前致谢。
使用 QuickCheck,您应该确定一些您认为应该适用于所有可能的输入/输出对的规则。如果您的输入概念只是“我传递给函数的字符串”而输出是“函数返回的切片”,则很难做到这一点。在这个抽象级别上,您真正可以编写的唯一规则是“函数应该生成由输入字符串表示的切片”——当然,您不能直接测试它,因为如果您从输入中进行了已知良好的转换输出你只是用它来代替。
相反,尝试在更细粒度的层面上思考。 给定输入的某些属性,您认为这个函数应该以哪些方式表现?这是我能想到的一些。
text
字符串的长度总和不应大于输入的大小。text
字符串之一中出现的所有字符都应出现在输入中的某处。n
变色序列,则输出中应该有n
切片。还是n-1
片?您打算如何表示“任何颜色变化序列之前的文本”?如果连续有两个颜色变化序列,它们之间没有文本怎么办?考虑到要测试的属性,我们已经在设计中找到了重要的边缘案例。这些属性需要不同级别的精度来测试它们。对于 (1),您甚至不需要 QuickCheck:您只需对单个空输入进行单元测试即可。对于 (2) 和 (3),您可以只传递一个任意字符串作为输入并比较输入和输出数据。但是 QuickCheck 的任意字符串生成器可能不会生成许多其中包含有意义的 ANSI 转义序列的字符串。所以你可能想要定义
stringWithEscapeSequences :: Gen String
或类似的东西,以确保你的测试输入是有趣的。
但是 (4) 更复杂。您 可以 将任意字符串作为输入,扫描它以查找转义序列,然后将其与
categorize
生成的切片数进行比较。但这需要在测试中包含大量函数的实现,并且函数中的错误很容易被测试中的错误所反映。一种不那么脆弱的方法是为测试用例编写一个数据类型,并为该类型编写一个生成器,这样您就可以提前知道每个测试用例需要多少个切片。有点像
data SliceCountTestCase = SliceCountTestCase
{ numSlices :: Int, input :: String }
deriving (Show, Eq)
instance Arbitrary SliceCountTestCase where
arbitrary = do
slices <- listOf slice
pure $ SliceCountTestCase (length slices) (serialize =<< slices)
where slice = (,) <$> color <*> listOf nonEscapeCharacter
serialize (c, body) = escapeSequence c ++ body
prop_sliceCountMatches :: SliceCountTestCase -> Bool
prop_sliceCountMatches (SliceCountTestCase n s) = length (categorize s) == n
该示例中有许多地方需要您填写:
color
、escapeSequence
和 nonEscapeCharacter
都是您需要根据您的领域知识编写的生成器。
您可以使用许多相同的组合器设计另一个类似的属性:给定切片列表作为输入,您应该能够将其编码为字符串,并且该字符串上的
categorize
应该会返回与开始时相同的结果与.