NAnt构建实例
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测试数据/
ReportGenerator: https://reportgenerator.codeplex测试数据/
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来生成报表
其他属性嘛,一目了然啊,不罗嗦啦。
五、完成NAnt构建配置
经过上述的配置,基本的自动化流程已经设置好啦。再根据需要进行一些细节处的调整。最终的.build文件如下:
View Code
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 >
后记
本来觉得没什么内容,还特意选择了比较简单的场景用来演示,结果写了一下午啊。好吧,我承认我的效率比较低,哈哈。
遗憾之处是还没有集成StyleCop或者FxCop,等我学会了集成它们,再更新这篇文章。
总之,希望此文对需要的朋友有帮助。
文章如有疏漏之处,望读者不吝赐教,板砖粪蛋尽管招呼。
作者: Leo_wl
出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息