循序渐进——NAnt构建实例

前言

NAnt,一款大名鼎鼎的.NET开源构建工具,功能强大,易于定制。

悲催的是开源的工具往往文档匮乏,广大程序猿们有时发现了看起来很酷的工具,可迟迟无法上手,时间就这么被残酷地浪费掉了。

在园子里搜索了一下,讲“持续集成”或者“每日构建”的不少,合我心意的不多,要么只能入门,要么起点太高。

正好这两天不忙,学习了一下NAnt的使用方法,下面就由我来通过一个实例,演示利用NAnt搭建一个自动化构建环境。

通过本文的构建,最终实现的效果为:

首先从SVN下载最新代码;利用NAnt编译代码;利用NUnit进行单元测试;生成单元测试结果报表以及代码覆盖率报表。

希望通过这篇文章,让打算使用NAnt进行自动化构建的同袍尽快上手。

本文中利用到的工具

NAnt(v0.92)

NUnit(v2.6.2)

OpenCover(v4.0.804)

ReportGenerator(v1.8.1.0)

TortoiseSVN(v1.7.10)

一、利用TortoiseSVN检出项目源码

1、获得工具

TortoiseSVN的项目地址如下:

http://sourceforge.net/projects/tortoisesvn/

2、安装工具

运行安装包,一路下一步即可。

3、检出源码

连接到你自己的代码服务器,检出源码。鉴于TortoiseSVN的易用性相当不错,我就不再罗嗦介绍具体的源码检出方法了,毕竟这并不是本文的重点。

本例中,假设代码服务器上面我们要构建的工程的地址为:http://192.168.1.1/myproject

假设源码检出到本地路径:D:\source\myproject

二、利用NAnt编译C#工程

1、获得工具

NAnt的项目地址如下:

NAnt:http://sourceforge.net/projects/nant/

NAntContrib:http://sourceforge.net/projects/nantcontrib/

NAnt不用多说。NAntContrib是NAnt的扩展,在本例中,需要利用它来生成单元测试报表和SVN控制。

2、安装工具

将NAnt的bin文件夹包含的文件拷贝出来,本例中放置在D:\Tools\NAnt

之后,将NAntContrib的bin文件夹包含的文件也拷贝到D:\Tools\NAnt

在任意位置,建立一个文件nant.bat,文件内容如下:

1 @echo off
2 "D:\Tools\NAnt\NAnt.exe" %*

然后,将nant.bat文件剪切到C:\WINDOWS目录下

运行cmd.exe,在命令行窗口中敲入命令“nant -help”,如果看到NAnt的帮助信息,则说明安装成功。

3、编译源码

首先,在刚刚检出的源码根目录(D:\source\myproject)下建立一个名字为myproject.build的xml文件。

文件内容如下:

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <project name="myproject" default="build" basedir=".">
 3     <property name="nant.settings.currentframework" value="net-3.5"/>
 4     <!-- 源码路径 -->
 5     <property name="dir.source" value="D:\source\myproject" />
 6     <property name="dir.source.myexe" value="${dir.source}\myexe" />
 7     <property name="dir.source.mylib" value="${dir.source}\mylib" />
 8     <property name="file.ico.myexe" value="${dir.source.exe}\myexe.ico" />
 9     <!-- 编译结果 -->
10     <property name="dir.release" value="D:\Release" />
11     <property name="dir.bin" value="${dir.release}\bin" />
12     <property name="file.exe.myexe" value="${dir.bin}\myexe.exe" />
13     <property name="file.lib.mylib" value="${dir.bin}\mylib.dll" />
14     <target name="build"
15             depends="compile">
16     </target>
17     <target name="compile"
18             depends="mylib,myexe">
19     </target>
20     <target name="mylib">
21         <csc target="library"
22              output="${file.lib.mylib}"
23              debug="Full"
24              optimize="true"
25              define="TRACE"
26              platform="AnyCPU"
27              warninglevel="4"
28              rebuild="true"
29              filealign="512">
30             <sources>
31                 <include name="${dir.source.mylib}\**\*.cs" />
32             </sources>
33         </csc>
34     </target>
35     <target name="myexe"
36             depends="mylib">
37         <csc target="winexe"
38              output="${file.exe.myexe}"
39              debug="Full"
40              optimize="true"
41              define="TRACE"
42              platform="AnyCPU"
43              warninglevel="4"
44              rebuild="true"
45              filealign="512"
46              win32icon="${file.ico.myexe}">
47             <sources>
48                 <include name="${dir.source.myexe}\**\*.cs" />
49             </sources>
50             <resources>
51                 <include name="${dir.source.myexe}\**\*.resx" />
52             </resources>
53             <references>
54                 <include name="${file.lib.mylib}" />
55             </references>
56         </csc>
57     </target>
58 </project>

这就是NAnt的构建配置文件了,下面对其中的内容说明一下:

文件主要由两种元素构成:property和target

property用来设置全局变量,以name属性作为唯一标识,使用的时候用${变量名}来引用。

除了自定义的property,NAnt自己也内建了一些全局变量,例如本例中出现的“nant.settings.currentframework”,用来指定当前工程使用的.NET Framework版本。

target是要执行的动作,同样适用name属性作为唯一标识,depends属性用来表示依存关系,例如

1 <target name="myexe"
2         depends="mylib">

上面的配置表示“myexe”这个target执行之前,要先保证mylib被执行了。

target内部可以包含很多Task标签,表示这个target要执行的任务,具体有哪些标签的可以参照NAnt的帮助文档。

最常用的就是csc标签,用来编译C#源码。

大部分csc的属性很好理解,这里强调几个需要特别注意的:

target:这个可不是外层的target标签哦,而是表示要生成什么类型的结果,本例中出现了library(类库)、winexe(窗口程序),还可以设置为exe(控制台程序)。

debug:设置成None的话,就只生成output指定的文件;如果设置成Full,则还会生成pdb文件,这个文件在我们下面进行代码覆盖率计算时需要,因此我们设置成Full。

csc的子标签常用的有三种,本例中都出现了,分别是sources(源码)、resources(资源文件)、references(引用),本文提供的实例应该很好理解,不做说明啦。

特别说明一下路径中使用到的通配符**和*,他们都表示任意文字,区别是**只能用于代表目录,并且可以代表任意级层次的目录,*可以代表目录与文件,但只能代表单级层次的内容,例如:

test1/test2/test3.cs和test1/test2/test3/test4.cs都可以被test1/**/*.cs匹配,而test1/*/*.cs只能匹配到test1/test2/test3.cs

OK,build文件做好了,现在再做一个build.bat文件,内容为:

1 cls
2 nant -buildfile:myproject.build -logfile:build.log

事实上,这两个参数都可选,只打一个命令“nant”也是可以的。

-buildfile参数用来指定build文件,如果不指定的话,会自动搜索当前目录下扩展名为.build的文件,如果存在多个.build文件,则只执行第一个。

-logfile参数用来输出构建过程中的日志,直观的说,就是我们在命令行窗口中看到的文字,都会被输出到指定的日志文件中。

三、利用NAnt自动更新代码

在我们文章的开始,我们是使用TortoiseSVN客户端来检出代码的,但我们想自动化,所以这个动作,也可以交给NAnt来完成。

1、修改.build文件

在.build文件中追加一个target,如下

1 <target name="update">
2     <svn command="update" 
3          destination="${dir.source}" 
4          uri="http://192.168.1.1/myproject" 
5          verbose="true"
6          quiet="false"
7          />
8 </target>

然后,再把update动作追加到动作序列里:

1 <target name="build"
2         depends="update,compile">

齐活儿~

四、利用NAnt进行单元测试并生成报表

1、获取工具

NUnit:http://sourceforge.net/projects/nunit/

OpenCover:https://opencover.codeplex.com/

ReportGenerator:https://reportgenerator.codeplex.com/

2、安装工具

NUnit直接执行安装文件,一路下一步。

将OpenCover的解压缩出来,本例中放置在D:\Tools\OpenCover

将ReportGenerator的bin文件夹包含的文件拷贝出来,本例中放置在D:\Tools\ReportGenerator

3、修改.build文件

在.build文件中追加target,如下

 1 <target name="mylib.test"
 2         depends="mylib">
 3     <csc target="library"
 4          output="${file.lib.mylib.test}"
 5          debug="None"
 6          optimize="true"
 7          define="TRACE"
 8          platform="AnyCPU"
 9          warninglevel="4"
10          rebuild="true"
11          filealign="512">
12         <sources>
13             <include name="${dir.source.mylib.test}\**\*.cs" />
14         </sources>
15         <references>
16             <include name="${file.lib.mylib}" />
17             <include name="${file.lib.nunit.framework}" />
18         </references>
19     </csc>
20     <copy todir="${dir.bin}" flatten="true">
21         <fileset>
22             <include name="${file.lib.nunit.framework}" />
23         </fileset>
24     </copy>
25 </target>
26 <target name="myexe.test"
27         depends="myexe">
28     <csc target="library"
29          output="${file.lib.myexe.test}"
30          debug="None"
31          optimize="true"
32          define="TRACE"
33          platform="AnyCPU"
34          warninglevel="4"
35          rebuild="true"
36          filealign="512">
37         <sources>
38             <include name="${dir.source.myexe.test}\**\*.cs" />
39         </sources>
40         <references>
41             <include name="${file.exe.myexe}" />
42             <include name="${file.lib.mylib}" />
43             <include name="${file.lib.nunit.framework}" />
44         </references>
45     </csc>
46     <copy todir="${dir.bin}" flatten="true">
47         <fileset>
48             <include name="${file.lib.nunit.framework}" />
49         </fileset>
50     </copy>
51 </target>
52 <target name="test"
53         depends="mylib.test,myexe.test">
54     <exec program="OpenCover.Console.exe" basedir="${dir.exe.opencover}">
55         <arg value="-register:user" />
56         <arg value="-target:${file.exe.nunit}" />
57         <arg value="-targetargs:${file.lib.myexe.test} ${file.lib.mylib.test} /result:${file.xml.test.result} /framework:net-3.5 /noshadow" />
58         <arg value="-output:${file.xml.test.coverage}" />
59     </exec>
60     <nunit2report format="NoFrames" todir="${dir.report}\NUnit" verbose="true">
61         <fileset>
62             <include name="${file.xml.test.result}" />
63         </fileset>
64     </nunit2report>
65     <mkdir dir="${dir.report}" />
66     <exec program="ReportGenerator.exe" basedir="${dir.exe.repotegenerator}">
67         <arg value="-reports:${file.xml.test.coverage}" />
68         <arg value="-targetdir:${dir.report}\OpenCover" />
69     </exec>
70 </target>

根据之前介绍的内容,这些配置比较好理解了,下面还是挑需要注意的地方讲解一下。

csc的debug属性设置成了None,这是因为测试工程生成的dll不需要进行覆盖率计算,因此不必生成pdb文件。

出现了copy标签,顾名思义,用来拷贝文件。

需要注意flatten属性,这个属性设置成true的意思是,拷贝的文件,不考虑原文件的目录结构,而是直接把原文件拷贝到目标文件夹下。如果设置成false,会把要拷贝的原文件的目录结构一起带过来的呦~

exec标签,用来执行一个外部程序。本例中用来调用OpenCover和ReportGenerator。

需要注意的地方:

1)NUnit是通过OpenCover来调用的,使用的是OpenCover的-target和-targetargs参数。

其中,-targetargs用来提供NUnit的执行参数,这里有点绕,希望注意。

2)NUnit可以同时对多个dll执行测试,多个dll之间用空格隔开。

3)nunit2report标签用来根据单元测试结果xml文件生成单元测试报表。

format属性用来设定报表的形式,NoFrames表示将单元测试结果使用一个html文件来展示;而Frames会把各个单元测试项结果分别生成一个html。本例中是采用了生成单一文件的形式。

4)OpenCover生成的代码覆盖率计算结果文件是一个xml,需要交给ReportGenerator来生成报表

5)ReportGenerator也可以同时处理多个xml文件,利用-reports参数,多个xml文件之间用分号隔开,例如:-reports:xml1;xml2

其他属性嘛,一目了然啊,不罗嗦啦。

五、完成NAnt构建配置

经过上述的配置,基本的自动化流程已经设置好啦。再根据需要进行一些细节处的调整。最终的.build文件如下:

  1 <?xml version="1.0" encoding="utf-8" ?>
  2 <project name="myproject" default="build" basedir=".">
  3     <property name="nant.settings.currentframework" value="net-3.5"/>
  4     <!-- 需要利用到的工具 -->
  5     <property name="dir.exe.opencover" value="D:\Tools\OpenCover" />
  6     <property name="dir.exe.repotegenerator" value="D:\Tools\ReportGenerator" />
  7     <property name="file.lib.nunit.framework" value="C:\Program Files\NUnit 2.6.2\bin\framework\nunit.framework.dll" />
  8     <property name="file.exe.nunit" value="C:\Program Files\NUnit 2.6.2\bin\nunit-console-x86.exe" />
  9     <!-- 源码路径 -->
 10     <property name="dir.source" value="D:\source\myproject" />
 11     <property name="dir.source.myexe" value="${dir.source}\myexe" />
 12     <property name="dir.source.myexe.test" value="${dir.source}\myexe.test" />
 13     <property name="dir.source.mylib" value="${dir.source}\mylib" />
 14     <property name="dir.source.mylib.test" value="${dir.source}\mylib.test" />
 15     <property name="file.ico.myexe" value="${dir.source.exe}\myexe.ico" />
 16     <!-- 编译结果 -->
 17     <property name="dir.release" value="D:\Release" />
 18     <property name="dir.bin" value="${dir.release}\bin" />
 19     <property name="file.exe.myexe" value="${dir.bin}\myexe.exe" />
 20     <property name="file.lib.myexe.test" value="${dir.bin}\myexe.test.dll" />
 21     <property name="file.lib.mylib" value="${dir.bin}\mylib.dll" />
 22     <property name="file.lib.mylib.test" value="${dir.bin}\mylib.test.dll" />
 23     <property name="file.pdb.myexe" value="${dir.bin}\myexe.pdb" />
 24     <property name="file.pdb.mylib" value="${dir.bin}\mylib.pdb" />
 25     <!-- 单元测试 -->
 26     <property name="dir.report" value="${dir.release}\report" />
 27     <property name="dir.result" value="${dir.release}\result" />
 28     <property name="file.xml.test.result" value="${dir.result}\myproject-results.xml" />
 29     <property name="file.xml.test.coverage" value="${dir.result}\myproject-coverage.xml" />
 30     <target name="build"
 31             depends="update,compile,test,clean">
 32     </target>
 33     <target name="update">
 34         <svn command="update" 
 35              destination="${dir.source}" 
 36              uri="http://192.168.1.1/myproject" 
 37              verbose="true"
 38              quiet="false"
 39              />
 40     </target>
 41     <target name="compile"
 42             depends="mylib,mylib.test,myexe,myexe.test">
 43     </target>
 44     <target name="mylib">
 45         <csc target="library"
 46              output="${file.lib.mylib}"
 47              debug="Full"
 48              optimize="true"
 49              define="TRACE"
 50              platform="AnyCPU"
 51              warninglevel="4"
 52              rebuild="true"
 53              filealign="512">
 54             <sources>
 55                 <include name="${dir.source.mylib}\**\*.cs" />
 56             </sources>
 57         </csc>
 58     </target>
 59     <target name="mylib.test"
 60             depends="mylib">
 61         <csc target="library"
 62              output="${file.lib.mylib.test}"
 63              debug="None"
 64              optimize="true"
 65              define="TRACE"
 66              platform="AnyCPU"
 67              warninglevel="4"
 68              rebuild="true"
 69              filealign="512">
 70             <sources>
 71                 <include name="${dir.source.mylib.test}\**\*.cs" />
 72             </sources>
 73             <references>
 74                 <include name="${file.lib.mylib}" />
 75                 <include name="${file.lib.nunit.framework}" />
 76             </references>
 77         </csc>
 78         <copy todir="${dir.bin}" flatten="true">
 79             <fileset>
 80                 <include name="${file.lib.nunit.framework}" />
 81             </fileset>
 82         </copy>
 83     </target>
 84     <target name="myexe"
 85             depends="mylib">
 86         <csc target="winexe"
 87              output="${file.exe.myexe}"
 88              debug="Full"
 89              optimize="true"
 90              define="TRACE"
 91              platform="AnyCPU"
 92              warninglevel="4"
 93              rebuild="true"
 94              filealign="512"
 95              win32icon="${file.ico.myexe}">
 96             <sources>
 97                 <include name="${dir.source.myexe}\**\*.cs" />
 98             </sources>
 99             <resources>
100                 <include name="${dir.source.myexe}\**\*.resx" />
101             </resources>
102             <references>
103                 <include name="${file.lib.mylib}" />
104             </references>
105         </csc>
106     </target>
107     <target name="myexe.test"
108             depends="myexe">
109         <csc target="library"
110              output="${file.lib.myexe.test}"
111              debug="None"
112              optimize="true"
113              define="TRACE"
114              platform="AnyCPU"
115              warninglevel="4"
116              rebuild="true"
117              filealign="512">
118             <sources>
119                 <include name="${dir.source.myexe.test}\**\*.cs" />
120             </sources>
121             <references>
122                 <include name="${file.exe.myexe}" />
123                 <include name="${file.lib.mylib}" />
124                 <include name="${file.lib.nunit.framework}" />
125             </references>
126         </csc>
127         <copy todir="${dir.bin}" flatten="true">
128             <fileset>
129                 <include name="${file.lib.nunit.framework}" />
130             </fileset>
131         </copy>
132     </target>
133     <target name="test"
134             depends="mylib.test,myexe.test">
135         <exec program="OpenCover.Console.exe" basedir="${dir.exe.opencover}">
136             <arg value="-register:user" />
137             <arg value="-target:${file.exe.nunit}" />
138             <arg value="-targetargs:${file.lib.myexe.test} ${file.lib.mylib.test} /result:${file.xml.test.result} /framework:net-3.5 /noshadow" />
139             <arg value="-output:${file.xml.test.coverage}" />
140         </exec>
141         <nunit2report format="NoFrames" todir="${dir.report}\NUnit" verbose="true">
142             <fileset>
143                 <include name="${file.xml.test.result}" />
144             </fileset>
145         </nunit2report>
146         <mkdir dir="${dir.report}" />
147         <exec program="ReportGenerator.exe" basedir="${dir.exe.repotegenerator}">
148             <arg value="-reports:${file.xml.test.coverage}" />
149             <arg value="-targetdir:${dir.report}\OpenCover" />
150         </exec>
151     </target>
152     <target name="clean">
153         <delete dir="${dir.result}" />
154         <delete>
155             <fileset>
156                 <include name="${file.lib.myexe.test}" />
157                 <include name="${file.lib.mylib.test}" />
158                 <include name="${file.pdb.myexe}" />
159                 <include name="${file.pdb.mylib}" />
160                 <include name="${dir.release}\bin\nunit.framework.dll" />
161             </fileset>
162         </delete>
163     </target>
164 </project>
View Code

后记

本来觉得没什么内容,还特意选择了比较简单的场景用来演示,结果写了一下午啊。好吧,我承认我的效率比较低,哈哈。

遗憾之处是还没有集成StyleCop或者FxCop,等我学会了集成它们,再更新这篇文章。

总之,希望此文对需要的朋友有帮助。

文章如有疏漏之处,望读者不吝赐教,板砖粪蛋尽管招呼。

posted @ 2013-05-29 17:51  高云鹏  阅读(2388)  评论(0编辑  收藏  举报