IntelliTest实战直通车(上集)
IntelliTest前世今生
IntelliTest的前身是微软研究院的白盒测试框架Pex,当时的Pex并未集成到Visual Studio中,开发者需要单独下载和配置,在下载时会附带一个叫Moles的隔离框架(它更早些的名字叫Stubs)。随着Visual Studio 2015 RC 版本的发布,Pex集成到Visual Studio中,成了IntelliTest,而Moles也升级成了Microsoft Fakes。
传统单元测试
回忆一下,写单元测试之前,我们的开发流程一般是怎样一个过程。
“我们很忙,一直忙着开发开发,眼看就要到deadline了,加紧继续开发开发。在这种情况下的产品质量可想而知,于是耐心的测试给我们报bug,善良的用户给我们提意见,没办法,要吃饭啊,我们又去改代码,然而代码并不是你想改,想改就能改的啊,为了少改出问题,往往要delay-delay-再delay。于是就陷入了一个我们写的代码越多,问题越多,delay也越严重的怪圈”
终于,在delay了28次后,我们爆发了,我们要它,对,就是它,单元测试!我们要写单元测试!
于是乎,我们的开发流程变成了类似下面这个样子:
多美好啊,开发效率高,交互质量也高,大家有饭吃,大家都开心(测试:“我XXX”)。
“咳咳!干啥呢!干啥呢!意淫个毛线啊,这个版本发了吗!看看覆盖率才多少!”
好吧,回到现实。
我们确实有开始写单元测试,也有做一些努力,但远远不够。我们针对相对稳定的公共组件,做了30%-50%的覆盖率,但是对上层相对复杂的业务逻辑几乎为0。而容易出问题,需要经常变更的,也恰恰是这些上层业务逻辑。这些业务代码可能经常变更,用例也需要随之变更,维护成本相对更高(虽然写了单元测试会降低维护成本,但这恰恰是多数开发者很难跨越的坎儿)。
IntelliTest
IntelliTest可以做什么?
IntelliTest通过探测你的.NET代码,自动生成一组高覆盖率的测试用例。虽然开发人员还是需要手动编写单元测试,但是IntelliTest可以确保对代码进行了充分的测试。
中文都能看懂,操作呢?
再回忆一下,我们是不是经常遇到这样的情况:为了覆盖不同的分支语句,我们需要写多个用例,然而这些用例中很多代码都是一样,每次都要做相同的arrange,相同的action,甚至相同的assert。对的,你可能会说,“NUnit已经提供了解决这个问题的方法啊,而且超级简单”,确实,在NUnit中,我们可以这样写用例:
[TestCase(12, 3, 4)]
[TestCase(12, 2, 6)]
[TestCase(12, 4, 3)]
public void DivideTest(int n, int d, int q)
{
Assert.AreEqual(q, n / d);
}
但是MSTest没有这么简单的方法啊,MSTest有的只是DataSource,而这又跟IntelliTest有什么关系呢?还真有那么点儿关系,MSTest虽然没有提供多参数的用例支持,但是随着IntelliTest的推出,提供了Parameterized Unit Tests[PUT](参数化单元测试),通过PUT,IntelliTest会自动为我们生成不同输入参数的用例,而且保证每个用例都是有意义的。
除此之外,IntelliTest能够在你修改了业务代码的情况下快速生成新的测试用例,大大减轻了测试用例的维护。同时这些测试用例都可以保存下来,供后续回归测试使用。
一个具体的栗子
有如下代码:
/// <summary>
/// 判断一个文件是否包含指定后缀
/// </summary>
/// <param name="fileName"></param>
/// <param name="suffix"></param>
/// <returns></returns>
public static bool IsSuffix(string fileName, string suffix)
{
var fileNameLower = fileName.ToLower();
if (fileNameLower.EndsWith(suffix))
return true;
return false;
}
我们右击它选择运行IntelliTest,接下来会弹出一个IntelliTest结果窗口,它是长下面这个样子滴:
解释一下各个名词的意思:
- 块(blocks):已覆盖Block数/IntelliTest探测到的总Block数(Block是啥?)
- 断言(asserts):PUT中运行过的断言数/PUT中定义的所有断言数
- 运行(runs):探测期间,IntelliTest尝试运行的次数
在运行结果窗口中我们看到,IntelliTest为我们生成了9个用例,其中4个通过,5个失败,并且给出了失败原因。点击每一条用例,在右侧会有此用例更加详细的信息(包括详细的参数和调用堆栈),便于我们跟踪分析错误原因。
全选后保存,IntelliTest自动添加一个测试工程并将用例放到PUT文件下面的g.cs文件中,后续可以继续利用这些用例做测试。实际上,微软并不希望我们编辑g.cs文件,在每次运行IntelliTest时,它会更新g.cs文件至最新。
多次提到PUT,有必要郑重介绍一下它了。所谓的“参数化单元测试”,指的是可以定义多组参数的单元测试方法。(哇,好郑重啊!),是的,就是这么简单直白。通过查看IntelliTest生成的文件很容易发现,在g.cs中的每一个用例最终都是通过调用PUT来实现测试的,这就是IntelliTest的参数化。在 传统单元测试 部分提到的NUnit的多参数测试就是NUnit的参数化单元测试,但是IntelliTest为我们实现了自动化。
然而,现实总是比想象复杂,我们的业务代码也远比栗子难嚼。在开发中使用IntelliTest会遇到的问题远比栗子中多,比如本篇未提到的IntelliTest结果窗口中的异常。IntelliTest的更多问题,下集继续。
date: 2017-10-29 09:02:23