IntelliTest(4) – Hands on[译]
在我们写传统单元测试时,为了达到一定的覆盖率,开发者需要反复的做一些类似的工作,比如,写测试用例,查看哪些代码未被覆盖,继续写测试用例覆盖之,直到所有的代码都被覆盖。
有了IntelliTest后,这一切都变得不一样了。我们会手把手的介绍IntelliTest技术,为了便于大家理解,会使用一个demo项目。
一、准备demo项目
按照上面给出的GitHub地址,克隆项目后,打开PokerLeagueManager.sln。这个demo主要用于跟踪统计每周的扑克牌联赛数据,每个玩家的游戏记录(玩过的游戏、总胜利次数、总筹码等)都会记录在一张表里。我们想要测试的方法在PokerLeagueManager.Queries.Core\EventHandlers\GetPlayerStatisticsHandler.cs\Handle
(GameDeletedEvent)里,代码片段如下:
这个方法做的事情是在一次游戏结束后更新相关玩家的数据。详细步骤如下:
- 获取在这次游戏的相关玩家
- 遍历这些玩家,并获取它们当前的统计数据
- 更新玩家数据
- 保存数据
正如我们在平时的开发,这个方法需要和其他对象交互。我们的目标就是让IntelliTest生成100%覆盖率的用例。
二、运行IntelliTest并理解它的警告
我们可以看到,只生成了两个用例,而且覆盖率极低(6/42blocks),还有5个警告。
点击警告按钮切换到警告界面。
第一个分类是“运行时警告”,将使用“PokerLeagueManager.Queries.Core.QueryDataStore
[PQCQ]” 作为接口“IQueryDataStore”的实例。通过查看代码,我们发现“IQueryDataStore”在基类BaseHandler中以属性的形式定义。为了测试Handle方法,我们必须要有一个IQueryDataStore的实例,这里,IntelliTest通过探测为我们推荐了PQCQ。
那么,PQCQ真的是我们需要的类型吗?
与此同时,IntelliTest探测到需要有一个公开的方法来初始化PQCQ,因为我们在测试Handle方法时需要用到PQCQ的实例,所以,在第二个警告分类“对象创建”的第一个警告就告诉我们它将会自动生成的对象构造API(一个工厂方法),如果愿意,我们可以保存这个工厂方法供测试方法调用。
当IntelliTest按照自己的方式真实的去构造PQCQ时,它发现它无法构造,并给出了原因。这便是“对象创建”分类中的第二个警告。
第三个分类叫做“无法探测的代码”,只有一个警告,从旁边的堆栈中我们可以看到,是DbContext的构造函数中的一些代码无法探测(如何探测?)。无法探测代码,有两个可能的原因:
- 引擎无法探测那部分代码
- 探测那部分代码会导致系统运行缓慢
有时候,IntelliTest探测分支需要运行非常多次的代码,甚至超过了系统为了保证性能设置的阈值。因此,它发出警告,并停止探测。
三、提供伪对象
在继续之前,我们需要回答一开始那个问题:
PQCQ真的是我们需要的类型吗?
显然它不是,为了测试Handle方法,我们需要一个IQueryDataStore的伪对象,在解决方案中,我们可以找到一个叫FakeQueryDataStore的类,这就是我们需要的伪对象。
为了完成IntelliTest,我们需要完善PUT方法。
点击警告按钮切换到测试窗口,全选用例并保存,然后删除掉.g.cs文件(这里面的用例并不符合我们的要求,它无法覆盖全部的代码)。剩下的GetPlayerStatisticsHandleTest.cs就是PUT方法的文件。
在测试项目中添加PokerLeagueManager.Common.Tests的引用,并且按照下面图片的红色指示完善代码。
再次运行IntelliTest(点击探测窗口中的运行,或在PUT方法上运行IntelliTest)
这次探索越界的警告就没了,我们可以看到有2个用例,4个警告。
查看警告我们发现,IntelliTest告诉我们它能够初始化PokerLeagueManager.Common.Tests.
FakeQueryDataStore[PCTF],以及我们可以使用的API。
当它使用这个API去初始化[PCTF]时,再次提示有无法探测的代码。在堆栈查看窗口中,我们可以跟踪到,无法探测的代码在GetData
选择“对象创建”类别,忽略里面的全部异常。
我们继续观察可以发现,在无法探测代码中,都是关于[PCTF]的,我们可以全部忽略。
我们可以在PexAssemblyInfo.cs中看到,增加了两行。
再次运行IntelliTest。
依然是两个用例,但是警告都没了。
通过分析Handle方法,我们知道,GetData必须返回一些数据我们才能覆盖余下的代码。(目前只覆盖了15/42 blocks)。
四、配置PUT
在PUT中,“target”是我们要测试的类对象。分析代码发现,GetData
继续添加引用PokerLeagueManager.Common.DTO,同时按照下面红色指示添加代码:
接下来,我们需要填充target的数据:
执行被测方法:
最后,我们只需要简单的查询统计数据并对数据的字段下断言:
因为我们改变了PUT,所以可以删除g.cs文件。
五、100%覆盖
再次运行IntelliTest。这次我们可以看到所有的代码都被覆盖了(52/52 blocks),并且有7个用例,3个通过,4个失败。还有7个警告。
分析警告可以知道,所有的警告都跟我们要测试的Handle方法无关,果断忽略之。
忽略后,可以看到,在PexAssemblyInfo.cs中增加了如下内容:
再次运行IntelliTest。这次我们可以看到代码全被覆盖,7个用例通过3个,并且没有警告。
其中两个未通过是对参数e未作判空处理,一个是除数出现0的情况(当Statistics.GamesPlayed=1时),还有一个是堆栈溢出,通过跟踪代码我们可以知道详细的用例。
至此,IntelliTest已经生成了所有用例,并发现了代码中的问题。如果我们增加更多的断言,会生成更多用例验证正确性。
date: 2017-10-20 11:43:52