Warning: Undefined variable $ex_word in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1174
Warning: Undefined variable $case in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1174
Warning: Undefined variable $ex_word in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1175
Warning: Undefined variable $case in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1175
Warning: Undefined variable $case in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1177
Warning: Undefined variable $ex_word in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1179
Warning: Undefined variable $ex_word in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1174
Warning: Undefined variable $case in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1174
Warning: Undefined variable $ex_word in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1175
Warning: Undefined variable $case in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1175
Warning: Undefined variable $case in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1177
Warning: Undefined variable $ex_word in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1179
Warning: Undefined variable $ex_word in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1174
Warning: Undefined variable $case in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1174
Warning: Undefined variable $ex_word in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1175
Warning: Undefined variable $case in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1175
Warning: Undefined variable $case in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1177
Warning: Undefined variable $ex_word in /www/wwwroot/www.bbqim8.com/wp-content/themes/justnews6.0.1/functions.php on line 1179
朋友们,我来了~
今天这一章还是讲测试,基于属性的测试(Property-Based Testing)。其实我感觉叫随机验证测试更通俗易懂一点。
我们日常自己测试的时候,会有一个问题,就是我们既是裁判,也是球员。因为我们的程序就是顺着我们的思路写的,测试也是按照同样的思路来测试的。所以,我们可能通常很难测出我们自己写出来的bug。
解决这个问题的一个办法,就是把测试交给其他人。这也是测试这个岗位存在的意义之一。但是,如果我们把测试交给了其他人,上一章提到的,对测试的思考可以帮助更好地设计程序,这个好处就不存在了。
基于属性测试
解决这个问题的办法就是,把其他人,换成我们的计算机,让计算机来帮我们自动测试。
之前关于契约或者合约(Contract)那一章中,提到了要给程序制定一个合同,规定了输入的规则、输出的规则、以及不变量(比如给一个数组排序,进去和出来的数组长度应该是不变的)。
那么合约和不变量在这里统称为属性(property),很抽象是不是?个人觉得它的定义并不重要,举个例子就懂了。
比如我们要验证一个list的排序,我们可以验证这两件事:1.输入和输出的list长度是否相等;2.是不是list里的每一个都比它前面一个大。
用python写出来就是这样:
from hypothesis import given
import hypothesis.strategies as some
@given(some.lists(some.Integers()))
def test_list_size_is_invariant_across_sorting(a_list):
original_length = len(a_list)
a_list.sort()
assert len(a_list) == original_length
@given(some.lists(some.text()))
def test_sorted_result_is_ordered(a_list):
a_list.sort()
for i in range(len(a_list) – 1):
assert a_list[i] <= a_list[i + 1]
更重要的是,它利用了hypothesis这个模块,@given(some.lists(some.integers()))会让它在运行的时候,利用随机的数值把同样的方法运行100次。它会把出现错误的情况记录下来。
对数器
之前在学习算法的时候,也接触到了一个叫做对数器的概念,其实和这里的基于属性测试,基本上是一件事情。
就是为了验证我们的算法A写对了,我们先写一个绝对正确的算法B,不管效率,只管正确。然后用同样的数据同时跑算法A和算法B。当然也是随机跑很多次啦。如果两个出现了不同的结果,那就说明算法A写错了。
思路基本相同,只不过,相对来说,可能这个基于属性测试更严谨一点,比如,同样是排序算法,我用我写的冒泡排序算法来验证我的选择排序算法,万一,我的冒泡排序也写错了呢?但是,如果我直接从根本上解析出排序(正序)就是后一个比前一个大,显然是更不容易出问题的,其实也能更进一步地锻炼我们寻找根本问题的能力。
当然啦,严格意义上来说,比较后一个比前一个大,这个本身也是一种算法。
实话说,要思考清楚有哪些属性是要测试的,这件事本身就充满了难度。如果你平时没有这个习惯,突然让你想,你会大脑一片空白的,就像是刚学编程那会,遇到了一个需求完全不知道从哪里下手的那种感觉。坦白的说,我现在就是这种状态,想必这也是需要刻意练习的。
这种随机大量测试的方式,可以帮助我们测出一些边界值,测出一些我们想不到的情况。往往最容易出问题的地方也是在边界值的地方。就跟开车似的,车开起来了,通常都没什么问题,但是起步可能会熄火,停车可能会倒不进去。
Java的相关框架
关于这个基于属性测试的框架,我随手搜了一搜,Python有的,没理由咱们Java没有,对不对?
一、找到两个github上开源:
1.https://github.com/HypothesisWorks/hypothesis-java
2.https://github.com/quicktheories/QuickTheories
二、找到一个都已经有自己网站的(虽然也有github):
https://jqwik.net/
三、还有一个直接是JUnit家的JUnit-Quickcheck(感觉上这个更香一点)
https://github.com/pholser/junit-quickcheck
我还没来得及仔细研究,朋友们可以先自行研究起来~
另一个实例
书中其实还提到了另外一个更加实际一点的例子,但我个人觉得那算不上bug,它和实际的需求有关系。这边简单提一下吧,或许你也有不同的见解。
大概就是有一个仓库类,它可以存放各种货品,不同货品有各自的数量,大概是这样一个结构吧:List<Map<String,Integer>>。然后我们可以查询某个货品是否有库存、查询某个货品还剩多少库存、可以从中取出某个数量的货品。
代码如下:
class Warehouse:
def __init__(self, stock):
self.stock = stock
def in_stock(self, item_name):
return (item_name in self.stock) and (self.stock[item_name] > 0)
def take_from_stock(self, item_name, quantity):
if quantity <= self.stock[item_name]:
self.stock[item_name] -= quantity
else:
raise Exception("Oversold {}".format(item_name))
def stock_count(self, item_name):
return self.stock[item_name]
仓库的初始化是这样的:
wh = Warehouse({"shoes": 10, "hats": 2, "umbrellas": 0})
然后,同样用了hypothesis来做批量测试,然后在调用take_from_stock( item_name='hats', quantity=3)这样一组数据的时候报错了。
作者说,在in_stock我们不应该只判断库存是不是大于0,而是要判断它有没有包含我们要拿取的货品数。代码应该改成这样:
def in_stock(self, item_name, quantity):
return (item_name in self.stock) and (self.stock[item_name] >= quantity)
反正我是觉得这不算个bug吧,毕竟在真正获取货品的时候,就报错了呀。要看我们对于in_stock这个方法本身的要怎么定义咯,是只需要知道它还有没有库存,还是需要知道它有没有我需要的库存。
虽然实际需求中,后者可能性更大,但是在take_from_stock方法里报错,又有什么问题呢?(又或者,作者只是想举个例子,是我太较真了)
尾声
基于属性的测试是对于单元测试的补充,对于单元测试的思考,可以让我们思考代码实现的其他方式。基于属性的测试,可以让我们更加清晰,我们的方法能干什么不能干什么,同时,也消除一些意外的情况。
如果,你还没有把这两种测试用起来,现在就赶紧用起来吧~
如若转载,请注明出处:https://www.bbqim8.com/archives/25944