Team版的VS2005里面包含了完整的Test功能,具体有:Unit Test,WebTest和LoadTest.这一整套的测试基本涵盖了软件开发会使用到的测试功能.
我这里先从单元测试开始介绍(Unit Test).说起单元测试,很多使用.net进行开发的人员也许马上就想起了NUnit,实际上它是个很好的工具,在VS2005出来之前,我也一直使用.不过现在VS2005已经提供了与NUnit一样,甚至还要强大的功能,我们又有什么理由不使用呢?
OK,进入正题.首先我们要解决一个问题,为什么我们需要做单元测试?这个问题,对有经验的老程序员来说,并不算是问题.一个软件在开发的过程中,倘若不是非常微型的软件,那么我们通常会划分出若干个功能模块来,然后一个模块一个模块的进行开发.每个子模块完成后,我们并不知道它是否能正常的运行,尤其是当这个模块只是个中间件类似的代码块,那么我们为了减少之后可能出现的问题和debug的难度(可以想象,如果在整合时期进行测试或者是甚至还有其他的模块需要依赖该模块才能进行开发的时候,尽早的测试将会是非常的重要),我们常常会对这单个模块进行测试,比如写段小程序,人为的写入几个参数来调用组件等.不用怀疑了,这就是单元测试.我相信,大部分的程序员都做过这样的工作,而且也许还有许多程序员就如我上面所说的,单独写段小程序来进行单元测试(我自己以前也是如此),现在我们需要认真考虑下下一个问题了:如何进行高效的,高可靠的,甚至自动化的单元测试?
VSTS里的Unit Test可以帮助我们实现我们希望的绝大多数功能.我们从实际的项目开发入手来介绍.假设我们新建了一个.net项目,嗯,这是一个有关缓存的子项目,名字叫MyCache.我们很认真的设计了项目的架钩,进行了可行性分析,接口和抽象的建立,具体对象的建立,关系建立,最后编码完成了.项目经理叫我们不要高兴的太早,他要求我们必须对这个项目进行可靠的单元测试,因为这个子项目非常重要,将会被许多项目引用.尽管我们很有信心,但是没有办法,我们依然需要进行单元测试.我们使用了Visual Studio Team System开发了这个项目,于是我们理所当然的使用自带的Unit Test工具进行单元测试.
Step1.我们需要建立项目文件与测试文件的映射关系.
难道要我们去手动创建吗?这可是整个项目啊,里面也许包含了几十个类,数百个方法…当然没那么复杂!实际上,我们需要做的工作很少,只是动动鼠标,等几秒就可以了:)
在VS2005的IDE环境下,选择menu里的Test,继续选New Test项,这时将跳出个窗体,里面可以选择测试项目类型,这里我们选择Unit Test Wizard,确定,输入测试项目名,然后将又出现一个窗体,里面包含你当前的solution里的所有project,我们选上我们的MyCache, 确定.OK,看见一个进度条,这是在执行测试代码的映射工作,等结束后,你就会发现,已经建立了一个测试项目了,里面的文件完全对应你的目标项目,每个类包含的方法也是与目标类的方法一一对应,非常简单,cool,mission complete!
Step2.运行我们的测试项目.
接下来,我们怎么进行测试呢?里面有许多的类和方法,很多方法上还带有像TestMethod这样的标签属性,但是我们关心的是,如何进行测试?绝对不是通常的F5来运行:(,在VSTS里,单元测试实际上有专门的管理工具.再次选到menu上的Test选项,移到windows上展开自菜单,里面有好几个选项,我们选择TestManager打开.在IDE窗口内出现了一个视图结构的东西,在分割线的右边是一个listView,里面全是当前测试项目包含的方法,我们随便选几个方法给勾上,右键,Run Checked Test,下边马上有出现了Test Result窗体,里面就是刚才你选择的方法.如果不出意外的话,你的这个窗体内的方法result应该都是failed之类的数据,嗯,先不管这个,最起码,我们已经运行了一次测试项目了,虽然有些奇怪,不过我们已经知道了如何运行一个测试项目了,那么再进入下一个step吧:)
Step3.看看我们的测试代码里都有些什么.
虽然知道了怎么运行测试项目,但莫名其妙的全部出错,是怎么回事呢?我们进入测试项目具体的代码来看看.
我们会发现,每个测试类的名称就是对应的目标类的名称+"test",里面的方法也是如此,如果是构造函数,则是诸如
ConstructorTest或ConstructorTestN这样的形式,N为重载次数.每个方法里面的代码看上去也不奇怪,只是构造参数来调用而已,最后通过断言来判断(用过NUnit的朋友不会陌生吧?).我们试着直接把一个方法里的断言去掉看看,编译,TestManager,run,嘿,果然,去掉断言的方法就pass了!看来蒙老大不难呢,只要把所有的方法的断言都给去掉,然后给老大看测试后的结果,呵呵…
Step4.深入的了解一下方法上带有的属性的含义.
每个方法上几乎都带有TestMethod这个属性,我们直觉告诉我们,这肯定是表示被测试函数的意思.事实也正是如此,在Unit Test里,有许多测试属性,常用的如下:
TestClass() |
该属性表示一个测试装置。 |
TestMethod() |
该属性表示一个测试用例。 |
AssemblyInitialize() |
在执行为执行选择的第一个 TestClass() 中的第一个 TestMethod() 之前,执行带有该属性的方法。 |
ClassInitialize() |
带有该属性的方法在执行第一个测试之前调用。 |
TestInitialize() |
带有该属性的方法在执行每个 TestMethod() 之前调用。 |
TestCleanup() |
带有该属性的方法在执行每个 TestMethod() 之后调用。 |
ClassCleanup() |
带有该属性的方法在执行 ALL 测试之后调用。 |
AssemblyCleanup() |
在执行为执行选择的第一个 TestClass() 中的第一个 TestMethod() 之后,执行带有该属性的方法。 |
Description() |
提供关于给定 TestMethod() 的描述。 |
Ignore() |
由于某种原因忽略 TestMethod() 或 TestClass()。 |
ExpectedException() |
当测试特定异常时,如果使用该属性指定的异常不是从实现代码引发,则测试不会失败。 |
需要注意的是,上面的属性不是可以适用于所有方法的,比如AssemblyInitialize()和ClassInitialize()是必须是静态方法的属性.
我们可以把初始化的操作放在他们里进行.
Step5.修改测试方法及其断言.
到现在,我们的思路开始清晰起来了,我们要开始做真正的测试了,不是仅仅去掉断言就pass那么简单了:)
我们的测试思路应该是这样:我们调用该方法,需要传入什么值,会影响什么值,当它执行之后,会产生怎样的期待值?我们把期待值与实际的值想比较,同时写下断言失败的message.
还是以我们的MyCahce为例,假如我们有个ListCache类,里面有个AddItemToTop(item)方法,表示把一个item插入到当前链表的头部.我们实际的测试函数该这么写
Guid id = System.Guid.NewGuid();
Item item = new Item(id);
list.AddItemToTop(item);
Assert.AreEqual(id, roomList.FirstLinkedItem.Key, "插入后查询获得的key值与插入的对象的key值不相等!");
通过比对插入后的链表的头部的key与之前保存的key值来判断,这是不是一次成功的插入.
这只是个很简单的例子,我们当然应该根据具体的方法需要实现的功能来定义测试代码.
Step6.OVER
完成了上面5部,相信你已经对VSTS的Unit Test非常的熟悉了,接下来需要做的就是把你需要的测试的method都提供正确的测试代码,注意,这里我们甚至不要考虑我们本身的项目究竟有没有实现该功能,但我们应该该知道,我们需要什么功能.我们只针对应该产生的结果写测试代码.当测试不通过时,我们只需要修改我们的目标项目,而不再需要修改我们的测试项目.这其实正是TDD(测试驱动开发)的思想,我们如果要验证我们的方法有没有错,只需要run一下test即可,真正实现了全自动化单元测试, 这里边的实际开发效率的提高,只有你在真正体会过后才能明白:)