IntelliTest(4) – Hands on[译]

  在我们写传统单元测试时,为了达到一定的覆盖率,开发者需要反复的做一些类似的工作,比如,写测试用例,查看哪些代码未被覆盖,继续写测试用例覆盖之,直到所有的代码都被覆盖。

  有了IntelliTest后,这一切都变得不一样了。我们会手把手的介绍IntelliTest技术,为了便于大家理解,会使用一个demo项目

一、准备demo项目

  按照上面给出的GitHub地址,克隆项目后,打开PokerLeagueManager.sln。这个demo主要用于跟踪统计每周的扑克牌联赛数据,每个玩家的游戏记录(玩过的游戏、总胜利次数、总筹码等)都会记录在一张表里。我们想要测试的方法在PokerLeagueManager.Queries.Core\EventHandlers\GetPlayerStatisticsHandler.cs\Handle
(GameDeletedEvent)里,代码片段如下:

这个方法做的事情是在一次游戏结束后更新相关玩家的数据。详细步骤如下:

  1. 获取在这次游戏的相关玩家
  2. 遍历这些玩家,并获取它们当前的统计数据
  3. 更新玩家数据
  4. 保存数据

正如我们在平时的开发,这个方法需要和其他对象交互。我们的目标就是让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的构造函数中的一些代码无法探测(如何探测?)。无法探测代码,有两个可能的原因:

  1. 引擎无法探测那部分代码
  2. 探测那部分代码会导致系统运行缓慢

  有时候,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方法中的T,需要是LookupGamePlayersDto和GetPlayerStatisticsDto类型,我们需要用数据填充PCTF的实例,这里,我们可以通过PUT配置,让IntelliTest为我们生成这些数据。我们需要在PUT的签名中,添加需要IntelliTest帮忙生成的数据。我们需要两个LookupGamePlayersDto实例和一个LookupGamePlayersDto实例。同时,我们要将统计数据和第一个玩家关联起来。

继续添加引用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已经生成了所有用例,并发现了代码中的问题。如果我们增加更多的断言,会生成更多用例验证正确性。

强烈建议阅读英文原文

-------------本文结束 感谢您的阅读-------------

本文标题:IntelliTest(4) – Hands on[译]

文章作者:nero

发布时间:2017年10月20日 - 11:10

最后更新:2017年10月31日 - 17:10

原始链接:http://erdao123.oschina.io/nero/2017/10/20/UnitTest/IntelliTest/05_Hands on/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。