单元测试 - _before和_after

  • 作者:KK

  • 发表日期:2015.12.13

  • 更新日期:2016.7.28

    补充了关于after删除测试数据的说明,引导读者了解Db模块来使用数据库镜像实现恢复数据状态


生成的单元测试都会自带一个protected function _before()的方法以及protected function _after()的方法

每一个testXXX运行之前都会跑一次_before以及运行之后都会跑一次_after

如果在这里写初始化数据的代码,那么假设运行2个测试方法就会初始化2次数据

但一般情况下这些数据只为一个测试方法做初始化准备而已,所以应该通过getName方法来获取当前要运行的测试方法名称来判断要初始化什么数据

假设测试用例有testA,testB,testC三个测试方法,那么运行流程如下:

  1. 运行_before

  2. 运行testA

  3. 运行_after

  4. 运行_before

  5. 运行testB

  6. 运行_after

  7. 运行_before

  8. 运行testC

  9. 运行_after

如果不声明这两个方法,父类也会有(那么其实声明了就是重写父类的而已),只是不做什么事情,在父类里是一个空方法来的,那什么时候才需要定义这两个方法呢?

假设A,B方法在没有_before的情况下都要先$user = new User($用户ID)或者通过其它更复杂的代码逻辑获得数据/对象,比如随机抽取数据库一条记录这样

这时候要考虑缩减重复代码,所以就将获取数据的方法封装成私有的方法,再A B两个方法共同调这个私有方法来获取数据,这是一个办法


然而还有一种情况,假设要测试一场比赛是否能正常进行,那么先要生成一场比赛,再进行测试断言,测试完毕后还需要将这场测试的比赛删除掉,不然每跑一次单元测试你的数据库就多一条比赛记录是不是能预见某天数据库有大量你不想要的数据?而且还只是测试的,所以就要删除掉咯

生成一场比赛可以写在私有方法里给A B方法共同调用,但是删除一场比赛呢?你要注意到一个细节,当断言失败时,测试不会再运行,所以如果你的代码如下:

public function testA(){
	$match = $this->_生成一场测试比赛();
	$this->assertTrue($match->start());
	$this->assertTrue($match->isPlaying);
	$this->assertTrue($match->addMember($this->user));
	$this->assertEquals(1, $match->getMemberCount());
	$this->_删除测试比赛();
}

如果中间$this->assertTrue($match->isPlaying)断言失败,根据单元测试的运行特性,会导致整个测试方法停下来,不再跑后面的代码,所以你无法执行到$this->_删除测试比赛()这里,可是数据产生了,可能会影响测试服务器的,其他同事会在测试服务器是莫名奇妙地见到些多余的数据,或者重复运行测试比赛,会累积越来越多的比赛数据,如果是别的测试场合,更加会遗留下不应该遗留的数据!

So,How to 办? --- 那可以告诉你,无论断言成功与否,跳出跳试方法后,_after依然会被运行,所以删除比赛数据的代码写在_after里就能确保它被运行了!

可问题是,根据上面列出的运行顺序,testC运行后也会跑一次_after,可是testC没有调用生成测试比赛的数据,那如果在_after里编写了删除数据的代码,A和B之后是正常能操作删除了,可是C的时候又运行就没必要了吧,而且删除时又报错说找不到要删除的数据咋办

也有解决办法————获取当前测试的方法可以用$this->getName(),那判断这个值是不是testA,testB,是的话就运行删除代码,不是的话就不做事就好了

提示:特殊情况下,当$this->getName()返回的不是一个字符串的方法名称时,请尝试改成$this->getName(false),详细不解释了,专业测试工程师才有必要掌握这个知识点

而又为了统一,我们也应该将初始化数据的调用代码放在_before里面,反正你真的有心想做好测试工作的话总会往这个方向走的


关于删除测试数据

其实在after里删除测试数据虽然可行,但是这样实际上一个项目中要写不少的删除测试数据代码,这挺麻烦的

其实我们可以省掉这一部分的工作,未来学到验收测试 - 基础 - 自动恢复测试数据的时候就行了(要先学会模块的配置控制)

既然不通过after删除数据,那用来做什么事,你自己发挥吧