走进单元测试(2):必须要自动化
前面讲到了现在被调到公司创新组做单元测试相关的工作,先交代下一些背景:
公司是一个做电子商务网站的公司,规模还可以。目前已经有了一套框架,该框架实现了ORM、同步、缓存等很多的功能;然后整个业务算是构建在这个框架之上的二次开发。我目前的任务是对这个业务代码构建一个相对完整的单元测试集。
既然提到单元测试,就不得不提自动化这一块。这里的自动化包含两层意思:首先,单元测试用例需要一个自动化执行的环境,即一个能够由外部条件触发而自动运行。其次是单元测试用例本身要能够自动化的进行,不能每次运行之前需要先配置好环境之类,也即能满足回归测试的要求。
对于第一个要求,我们使用的是CruiseControl.NET这个持续集成工具来进行自动化的配置。这个工具的具体用法这里不介绍了,具体可参见http://ccnetlive.thoughtworks.com/。注意到我们现在建立测试并不是走的测试驱动开发流程,因此可以采用一个定时的方式来发动集成请求,我们把这个时间设置在半夜,具体做的事情如下:先去SVN上取出最新的源代码和测试代码,然后编译,最后使用NUnit运行生成的测试dll。
下面是一个ccnet配置示例,有兴趣的可以拿来直接去用了
2 <!-- This is your CruiseControl.NET Server Configuration file. Add your projects below! -->
3 <project name="" queuePriority="1" queue="Q1">
4 <workingDirectory>D:\ccnet</workingDirectory>
5 <artifactDirectory>D:\ccnet</artifactDirectory>
6 <category>Unit Test</category>
7
8 <sourcecontrol type="multi">
9 <sourceControls>
10 <svn>
11 <trunkUrl></trunkUrl>
12 <workingDirectory>D:\ccnet\source\</workingDirectory>
13 <executable>C:\Program Files (x86)\Subversion\bin\svn.exe</executable>
14 <username></username>
15 <password></password>
16 <cleanCopy>true</cleanCopy>
17 <timeout>60000</timeout>
18 </svn>
19
20 <svn>
21 <trunkUrl></trunkUrl>
22 <workingDirectory>D:\ccnet\source\UT</workingDirectory>
23 <executable>C:\Program Files (x86)\Subversion\bin\svn.exe</executable>
24 <username></username>
25 <password></password>
26 <cleanCopy>true</cleanCopy>
27 <timeout>60000</timeout>
28 </svn>
29 </sourceControls>
30 </sourcecontrol>
31
32 <tasks>
33 <modificationWriter>
34 <filename>changelist.xml</filename>
35 <path>D:\ccnet\changelist</path>
36 <appendTimeStamp>true</appendTimeStamp>
37 </modificationWriter>
38
39 <msbuild>
40 <executable>C:\Windows\Microsoft.NET\Framework\v3.5\MSBuild.exe</executable>
41 <workingDirectory>D:\ccnet</workingDirectory>
42 <projectFile></projectFile>
43 <timeout>600</timeout>
44 <logger>C:\Program Files (x86)\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger>
45 </msbuild>
46
47 <nunit>
48 <path>C:\Program Files (x86)\NUnit 2.5\bin\net-2.0\nunit-console.exe</path>
49 <assemblies>
50 <assembly></assembly>
51 </assemblies>
52 </nunit>
53 </tasks>
54
55 <triggers>
56 <scheduleTrigger time="23:30" name="UnitTestSchedule" buildCondition="ForceBuild">
57 <weekDays>
58 <weekDay>Monday</weekDay>
59 <weekDay>Tuesday</weekDay>
60 <weekDay>Wednesday</weekDay>
61 <weekDay>Thursday</weekDay>
62 <weekDay>Friday</weekDay>
63 </weekDays>
64 </scheduleTrigger>
65 </triggers>
66
67 <publishers>
68 <modificationHistory onlyLogWhenChangesFound="true"/>
69 <xmllogger logDir="D:\ccnet\buildlogs"/>
70
71 <email from="" mailhost="" mailport="25" includeDetails="true" mailhostUsername="" mailhostPassword="">
72 <users>
73 <user name="Billy" group="buildmasters" address="" />
74 </users>
75 <groups>
76 <group name="buildmaster" notification="always" />
77 </groups>
78 <subjectSettings>
79 <subject buildResult="StillBroken" value="Build is still broken for ${CCNetProject}, the fix failed." />
80 <subject buildResult="Broken" value="{CCNetProject} broke at ${CCNetBuildDate} ${CCNetBuildTime } , last checkin(s) by ${CCNetFailureUsers}" />
81 <subject buildResult="Exception" value="Serious problem for ${CCNetProject}, it is now in Exception! Check status of network / sourcecontrol" />
82 </subjectSettings>
83 </email>
84 </publishers>
85 </project>
86 </cruisecontrol>
(我这里使用的是MSBuild,也可以使用VS的solution直接编译,详见ccnet帮助)
对于第二个自动化要求,这个本身对被测代码的要求比较高。比如很明显的就是现在都采用了分层架构,但这段代码到底属于哪个层,很多人却并不是很清楚。举个例子:业务逻辑层的代码中出现了HttpContext这个东西,那么对单元测试的编写就是个很大的烦恼;再比如cookie等的设置。至于原因,从第一条可以看到要保证这个测试能自动化,那么其运行环境不能依赖于IIS或者WebServer,因此这也是单元测试设计工作中最难的地方,后面将会有很大的篇幅讲述如何消除掉这些依赖或者模拟出一个环境来。
可能有人会问为什么要自动化,同样从上面的两个方面来说。第一个方面的自动化,用Email通知下相关人员可以很快知道测试中有些什么问题,需要他们的协助。第二个方面的自动化则是出于第一个方面的需求,因为如果每次测试都要人工干预的话会很麻烦,成本很高。