• 循序渐进——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,等我学会了集成它们,再更新这篇文章。

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

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

  • 相关阅读:
    面试题八 二进制中 1 的个数
    面试题七 斐波那契数列
    面试题六 用两个栈实现队列
    第 3 章 第 2 题 求级数问题 递归法实现
    第 3 章 第 1 题 精简冗余 if 语句问题 使用数组实现
    第 2 章 第 10 题 测量电灯泡体积问题
    第 2 章 第 9 题 顺序 & 二分搜索效率分析问题
    带缓冲的IO( 标准IO库 )
    Linux 中权限控制实例
    Linux 中权限的再讨论( 下 )
  • 原文地址:https://www.cnblogs.com/gaoyunpeng/p/3106439.html
Copyright © 2020-2023  润新知