我想知道属性测试的目的是什么,它的最佳点是什么,应该在哪里使用。让我们有一个我想测试的示例函数:
f :: [Integer] -> [Integer]
这个函数,f
,接受一个数字列表,并将奇数平方并过滤掉偶数。我可以说明有关该功能的一些属性,例如
- 给定一个偶数列表,返回空列表。
- 给定一个奇数列表,结果列表将与输入具有相同的大小。
- 鉴于我有一个偶数列表和一个奇数列表,当我加入它们时,随机播放并传递给函数,结果的长度将是奇数列表的长度。
- 给定我提供了一个正奇数列表,那么结果列表中相同索引处的每个元素都将大于原始列表中的元素
- 鉴于我提供了一个奇数和偶数的列表,加入并打乱它们,然后我会得到一个列表,其中每个数字都是奇数
- 等等
没有任何属性测试,该函数适用于最简单的情况,例如我可以做一个简单的情况,如果我实现f
不正确,它将传递这些属性:
f = fmap (+2) . filter odd
因此,如果我想涵盖一些简单的案例,看起来我要么需要在属性规范中重复算法的基本部分,要么需要使用基于值的测试。第一个选项,我有,重复算法可能有用,如果我打算改进算法,如果我打算改变它的实现,例如速度。这样,我就有了一个参考实现,我可以用它来再次测试。
如果我想检查一下,算法在一些琐碎的情况下不会失败,并且我不想在规范中重复该算法,看起来我需要一些单元测试。例如,我会写这些检查:
f ([2,5]) == [25]
f (-8,-3,11,1) == [9,121,1]
现在我对算法更有信心了。
我的问题是,基于属性的测试是要取代单元测试,还是互补?是否有一些一般的想法,如何编写属性,所以它们是有用的,或者它完全取决于对函数逻辑的理解?我的意思是,可以说以某种方式编写属性特别有益吗?
另外,是否应该努力让属性测试算法的每个部分?我可以将平方排除在算法之外,然后在其他地方进行测试,让属性只测试过滤部分,它看起来像,它很好地覆盖了它。
f :: (Integer -> Integer) -> [Integer] -> [Integer]
f g = fmap g . filter odd
然后我可以通过Prelude.id
并g
使用单元测试测试其他地方。