一篇不错的文章:http://www.codeproject.com/KB/architecture/ContinuousIntegration.aspx?msg=1697597#xx1697597xx,
用CC.NET主要用来控制代码控制,编译,自动执行UnitTest。
Background
- Source control repository to store code in a common location, and allow multiple developers to share a code base (e.g. P4, CVS, Subversion, VSS).
- Unit testing framework to run unit tests which are written as part of the code development process, and can flag unintentional changes to code (e.g. NUnit, JUnit).
- Automated build tool to allow building from the command line, and to perform related tasks (e.g. Ant, NAnt, MSBuild).
- Continuous Integration tool which monitors for code changes and triggers builds (e.g. CruiseControl, Draco).
Flow Control:
- Developer commits code changes/additions to CVS/P4.
- CruiseControl.NET detects changes.
- CruiseControl.NET updates code on build machine.
- CruiseControl.NET invokes NAnt script.
- NAnt script builds application using devenv.com
- NAnt script runs unit tests Final CruiseControl.NET tasks support display of unit testing information.
- CruiseControl.NET manages output files for display in its Web Dashboard.
- CruiseControl.NET sends emails as appropriate.
主要包括的配置文件C:/Program Files/CruiseControl.NET/server/ccnet.config(一个),default.build(一个Main Build File, 几个Project Level Build File)。
例子如下:
1. ccnet.config :
1)定义一个Main Project Build,以及UnitTest Project,建立二者之间的关系Main Project----->Trigger--->Unit Test Project(比如是有先后依赖的关系)
2)依赖关系例子建立如下:
<triggers>
<multiTrigger operator="Or">
<triggers>
<projectTrigger project="Project1">
<triggerStatus>Success/Failure</triggerStatus>
</projectTrigger>
</triggers>3)queuePriority数字越小,优先级别越高,所以queuePriority for Main应该小于Test,同时放在两个不同的Queue(Main/Test)
4)针对Unit Test Project,需要在Dashboard显示Unit Test Result(可能有多个Assembly Dll),所以要手动进行Multiple File Merge的配置(文件命名规则要配合default.build的定义)
<merge>
<files>
<!-- these file names need to conform to those found in default.build -->
<file>C:/Perforce/Projects/Project1/Test/UnitTest*.xml</file>
</files>
</merge>*****************************
<!-- main -->
<project name="Project1" queue="Main" queuePriority="100">
<labeller type="defaultlabeller">
<prefix>2.0.</prefix>
</labeller>
<workingDirectory>C:/Projects/Project1/main</workingDirectory>
<artifactDirectory>E:/CruiseControl.NET/Project1/Artifacts</artifactDirectory>
<webURL>http://XXX/ccnet/server/local/project/Project1/ViewLatestBuildReport.aspx</webURL>
<triggers>
<intervalTrigger name="continuous" seconds="30" buildCondition="IfModificationExists"/>
</triggers>
<!-- CHECK FOR CHANGES IN PERFORCE, EXCLUDE .CHM FILES SINCE THEY ARE CHECKED IN AS PART OF THIS BUILD -->
<sourcecontrol type="filtered">
<sourceControlProvider type="p4">
<view>//Projects/Project1/main...</view>
<client>XXX</client>
<user>test</user>
<password>test</password>
<port>perforce:1666</port>
<applyLabel>false</applyLabel>
<autoGetSource>true</autoGetSource>
<forceSync>false</forceSync>
</sourceControlProvider>
<exclusionFilters>
<pathFilter>
<pattern>/**/*.chm</pattern>
</pathFilter>
</exclusionFilters>
</sourcecontrol>
<tasks>
<!-- RE-BUILD ALL THE CODE -->
<nant>
<executable>C:/Program Files/nant-0.85/bin/nant.exe</executable>
<baseDirectory>C:/Projects/Project1/main</baseDirectory>
</nant>
<buildpublisher>
<sourceDir>C:/Projects/Project1/main/Web</sourceDir>
<publishDir>E:/CruiseControl.NET/Project1/Artifacts</publishDir>
<useLabelSubDirectory>true</useLabelSubDirectory>
</buildpublisher>
</tasks>
<publishers>
<!-- SPECIFY THE PUBLIC FOLDER THAT THE LOGS ARE IN FOR THE WEB DASHBOARD TO USE -->
<xmllogger logDir="C:/Program Files/CruiseControl.NET/webdashboard/buildlogs/Project1"/>
<!--<artifactcleanup cleanUpMethod="KeepLastXBuilds" cleanUpValue="20" />
<artifactcleanup cleanUpMethod="KeepLastXSubDirs" cleanUpValue="20" />-->
<!-- NOTIFY USERS VIA EMAIL -->
<email from="buildresult@test.com" mailhost="localhost" includeDetails="TRUE">
<users>
<user name="test0" group="projectleader" address=test0@test.com/>
<user name="test1" group="buildmaster" address=test1@test.com/>
<user name="test2" group="developers" address=test2@test.com/>
</users>
<groups>
<group name="developers" notification="failed"/>
<group name="buildmaster" notification="always"/>
<group name="projectleader" notification="change"/>
</groups>
</email>
</publishers>
</project>*****************************
<!-- Test -->
<project name="Project1Test" queue="Test" queuePriority="200">
<labeller type="defaultlabeller">
<prefix>2.0.</prefix>
</labeller>
<workingDirectory>C:/Perforce/Projects/Project1/Test</workingDirectory>
<artifactDirectory>E:/CruiseControl.NET/Project1Test/Artifacts</artifactDirectory>
<webURL>http://XXX/ccnet/server/local/project/Project1Test/ViewLatestBuildReport.aspx</webURL>
<triggers>
<multiTrigger operator="Or">
<triggers>
<projectTrigger project="Main">
<triggerStatus>Success</triggerStatus>
</projectTrigger>
</triggers>
</multiTrigger>
</triggers>
<!-- CHECK FOR CHANGES IN PERFORCE, EXCLUDE .CHM FILES SINCE THEY ARE CHECKED IN AS PART OF THIS BUILD -->
<sourcecontrol type="filtered">
<sourceControlProvider type="p4">
<view>//Projects/Project1/Test...</view>
<client>test</client>
<user>test</user>
<password>test</password>
<port>perforce:1666</port>
<applyLabel>false</applyLabel>
<autoGetSource>true</autoGetSource>
<forceSync>false</forceSync>
</sourceControlProvider>
<exclusionFilters>
<pathFilter>
<pattern>/**/*.chm</pattern>
</pathFilter>
</exclusionFilters>
</sourcecontrol>
<tasks>
<!-- RE-BUILD ALL THE CODE -->
<nant>
<executable>C:/Program Files/nant-0.85/bin/nant.exe</executable>
<baseDirectory>C:/Perforce/Projects/Project1/Test</baseDirectory>
</nant>
<buildpublisher>
<sourceDir>C:/Perforce/Projects/Project1/Test</sourceDir>
<publishDir>E:/CruiseControl.NET/Project1Test/Artifacts</publishDir>
<useLabelSubDirectory>true</useLabelSubDirectory>
</buildpublisher>
</tasks>
<publishers>
<merge>
<files>
<!-- these file names need to conform to those found in default.build -->
<file>C:/Perforce/Projects/Project1/Test/UnitTest*.xml</file>
</files>
</merge>
<!-- SPECIFY THE PUBLIC FOLDER THAT THE LOGS ARE IN FOR THE WEB DASHBOARD TO USE -->
<xmllogger logDir="C:/Program Files/CruiseControl.NET/webdashboard/buildlogs/Project1Test"/>
<!-- NOTIFY USERS VIA EMAIL -->
<email from="buildresult@test.com" mailhost="localhost" includeDetails="TRUE">
<users>
<user name="test0" group="projectleader" address=test0@test.com/>
<user name="test1" group="buildmaster" address=test1@test.com/>
<user name="test2" group="developers" address=test2@test.com/>
</users>
<groups>
<group name="developers" notification="failed"/>
<group name="buildmaster" notification="always"/>
<group name="projectleader" notification="change"/>
</groups>
</email>
</publishers>
</project>2. Main Project default.build(包括Main以及Sub Projects)
1)Main Build File(C:/Perforce/Projects/Project1/main/default.build)
按照inclue name进行先后编译,所以入排列时要注意顺序的。
********************************
<?xml version="1.0" encoding="utf-8"?>
<project name="Project1" default="build">
<target name="build">
<nant target="build">
<buildfiles>
<include name="sub-project1/default.build" />
<include name="sub-project2/default.build" />
<!--more project builds can be added here -->
</buildfiles>
</nant>
<call target="deploy" />
</target><target name="deploy">
<copy todir="Web/bin" overwrite="true">
<fileset basedir="ReportUtility/bin/">
<include name="*.dll"/>
</fileset>
</copy>
</target>
</project>2) Sub Project Build File(C:/Perforce/Projects/Project1/main/sub-project1/default.build)
如果对于Satelite Assembly,需要特别注意(比如多语言的资源文件会生成多个,bin/pt-PT/***.resources.dll, bin/jp-JP/***.resources.dll).
<resources dynamicprefix="true">
<include name="App_GlobalResources/Strings.resx"/>
<include name="App_GlobalResources/Strings.pt-PT.resx"/>
</resources>*****************************
<?xml version="1.0"?>
<project name="Project1BusinessLayer" default="build">
<property name="basename" value="SubProject1.Assembly"/>
<property name="debug" value="true"/>
<property name="uptodate" value="true"/><target name="clean">
<delete>
<fileset>
<include name="bin/${basename}.dll"/>
<include name="bin/${basename}.pdb"/>
</fileset>
</delete>
</target><target name="build">
<uptodate property="uptodate">
<sourcefiles>
<include name="*.cs"/>
</sourcefiles>
<targetfiles>
<include name="bin/${basename}.dll"/>
</targetfiles>
</uptodate>
<!--
<csc target="library" output="bin/${basename}.dll" debug="${debug}">
<sources>
<include name="*.cs"/>
<exclude name="AssemblyInfo.cs"/><if test="${property::exists('CCNetLabel') and not uptodate}">--> //避免该组件的版本号码有时与整个Solution的不一致,导致组件升级,用户Download时发生错误,特别是SmartClient的情况。
<asminfo output="FMAssemblyInfo.cs" language="CSharp">
<imports>
<import namespace="System" />
<import namespace="System.Reflection" />
<import namespace="System.Runtime.InteropServices" />
</imports>
<attributes>
<attribute type="ComVisibleAttribute" value="false" />
<attribute type="AssemblyVersionAttribute" value="${CCNetLabel}" />
<attribute type="AssemblyTitleAttribute" value="${basename}" />
<attribute type="AssemblyDescriptionAttribute" value="Project1" />
<attribute type="AssemblyCopyrightAttribute" value="Copyright © Test 2007" />
</attributes>
</asminfo><!--
</if>-->
<include name="Properties/*.cs"/>
</sources><resources dynamicprefix="true">
<include name="Resource.pt-PT.resx"/>
<include name="Resource.resx"/>
</resources>
<references>
<include name="../CommonDlls/*.dll"/>
<include name="C:/Program Files/log4net-1.2.10/*.dll"/>
</references>
</csc>
</target>
</project>*****************************
一个Smart Client的Publish 的脚本例子
<?xml version="1.0"?>
<project name="SmartClient1" default="build">
<property name="basename" value="SmartClient1"/>
<property name="debug" value="true"/>
<property name="date.now" value="${ datetime::now() }"/>
<property name="uptodate" value="true"/>
<property name="publishdir" value="C:/Perforce/Projects/Project1/main/Web/SmartClient1/" />
<property name="log4netDir" value="C:/Program Files/log4net-1.2.10/" />
<property name="projectname" value="C:/Perforce/Projects/Project1/main/SmartClient1/SmartClient1.csproj" />
<target name="clean">
<delete>
<fileset>
<include name="bin/${basename}.exe"/>
<include name="bin/${basename}.pdb"/>
</fileset>
</delete>
</target><target name="build">
<uptodate property="uptodate">
<sourcefiles>
<include name="*.cs"/>
</sourcefiles>
<targetfiles>
<include name="bin/${basename}.exe"/>
</targetfiles>
</uptodate>
<!--
<if test="${property::exists('CCNetLabel') and not uptodate}">-->
<delete file="Properties/AssemblyInfo.cs"></delete>
<asminfo output="Properties/AssemblyInfo.cs" language="CSharp">
<imports>
<import namespace="System" />
<import namespace="System.Reflection" />
<import namespace="System.Runtime.InteropServices" />
</imports>
<attributes>
<attribute type="ComVisibleAttribute" value="false" />
<attribute type="AssemblyVersionAttribute" value="${CCNetLabel}" />
<attribute type="AssemblyTitleAttribute" value="SmartClient1" />
<attribute type="AssemblyDescriptionAttribute" value="SmartClient1" />
<attribute type="AssemblyCopyrightAttribute" value="Copyright © test" />
<attribute type="AssemblyCompanyAttribute" value="Test" />
<attribute type="AssemblyProductAttribute" value="SmartClient1" />
</attributes>
</asminfo><!--
</if>-->
<mkdir dir="bin" />
<csc target="exe" output="bin/${basename}.exe" debug="${debug}">
<sources>
<include name="*.cs"/>
<include name="Web References/WS/*.cs"/>
<include name="Properties/*.cs"/>
<include name="UserControls/*.cs"/>
<exclude name="AssemblyInfo.cs"/>
</sources>
<resources dynamicprefix="true">
<include name="Resource.pt-PT.resx"/>
<include name="Resource.resx"/>
</resources>
<references>
<include name="C:/Program Files/log4net-1.2.10/*.dll"/>
<include name="../CommonDlls/*.dll"/>
</references>
</csc>
<delete dir="C:/Perforce/Projects/Project1/main/Web/SmartClient1/" />
<!--take backup of config-->
<copy todir="config_backup" overwrite="true">
<fileset basedir=".">
<include name="*.config"/>
</fileset>
</copy>
<msbuild target="Publish" project="${projectname}">
<property name="PublishDir" value="${publishdir}/UnitTest/" />
<property name="InstallUrl" value="http://localhost/Project1/SmartClient1/" />
<property name="UpdateUrl" value="http://localhost/Project1/SmartClient1/" />
<property name="ProductName" value="SmartClient1" />
<property name="AssemblyName" value="SmartClient1" />
</msbuild>
<!--take backup of bin for NUnit test-->
<copy todir="bin_backup" includeemptydirs="true" overwrite="true" verbose="true">
<fileset basedir="./bin">
<include name="**/*.*"/>
</fileset>
</copy>
<!--For test server-->
<move file="app_test.config" tofile="app.config" overwrite="true" />
<!-- Delete bin and obj directories -->
<delete dir="bin" />
<delete dir="obj" />
<copy todir="bin" overwrite="true">
<fileset basedir="${log4netDir}">
<include name="*.dll"/>
</fileset>
</copy>
<copy todir="bin" overwrite="true">
<fileset basedir="../CommonDlls/ZipUtility/">
<include name="*.dll"/>
</fileset>
</copy>
<msbuild target="Publish" project="${projectname}">
<property name="PublishDir" value="${publishdir}/Test/" />
<property name="InstallUrl" value="http://TestServer/Project1/SmartClient1/" />
<property name="UpdateUrl" value="http://TestServer/Project1/SmartClient1/" />
<property name="ProductName" value="SmartClient1" />
<property name="AssemblyName" value="SmartClient1" />
</msbuild>
<!--For staging server-->
<move file="app_staging.config" tofile="app.config" overwrite="true" />
<!-- Delete bin and obj directories -->
<delete dir="bin" />
<delete dir="obj" />
<copy todir="bin" overwrite="true">
<fileset basedir="${log4netDir}">
<include name="*.dll"/>
</fileset>
</copy>
<copy todir="bin" overwrite="true">
<fileset basedir="../CommonDlls/ZipUtility/">
<include name="*.dll"/>
</fileset>
</copy>
<msbuild target="Publish" project="${projectname}">
<property name="PublishDir" value="${publishdir}/Staging/" />
<property name="InstallUrl" value="http://www.test.com/Project1/SmartClient1/" />
<property name="UpdateUrl" value="http://www.test.com/Project1/SmartClient1/" />
<property name="ProductName" value="SmartClient1" />
<property name="AssemblyName" value="SmartClient1" />
</msbuild>
<!--For production server-->
<move file="app_production.config" tofile="app.config" overwrite="true" />
<!-- Delete bin and obj directories -->
<delete dir="bin" />
<delete dir="obj" />
<copy todir="bin" overwrite="true">
<fileset basedir="${log4netDir}">
<include name="*.dll"/>
</fileset>
</copy>
<copy todir="bin" overwrite="true">
<fileset basedir="../CommonDlls/ZipUtility/">
<include name="*.dll"/>
</fileset>
</copy>
<msbuild target="Publish" project="${projectname}">
<property name="PublishDir" value="${publishdir}/Production/" />
<property name="InstallUrl" value="https://www.production.com/Project1/SmartClient1/" />
<property name="UpdateUrl" value="https://www.production.com/Project1/SmartClient1/" />
<property name="ProductName" value="SmartClient1" />
<property name="AssemblyName" value="SmartClient1" />
</msbuild>
<!--For training server-->
<move file="app_training.config" tofile="app.config" overwrite="true" />
<!-- Delete bin and obj directories -->
<delete dir="bin" />
<delete dir="obj" />
<copy todir="bin" overwrite="true">
<fileset basedir="${log4netDir}">
<include name="*.dll"/>
</fileset>
</copy>
<copy todir="bin" overwrite="true">
<fileset basedir="../CommonDlls/ZipUtility/">
<include name="*.dll"/>
</fileset>
</copy>
<msbuild target="Publish" project="${projectname}">
<property name="PublishDir" value="${publishdir}/Training/" />
<property name="InstallUrl" value="https://www.training.com/Project1/SmartClient1/" />
<property name="UpdateUrl" value="https://www.training.com/Project1/SmartClient1/" />
<property name="ProductName" value="SmartClient1" />
<property name="AssemblyName" value="SmartClient1" />
</msbuild>
<!-- Delete bin and obj directories --><!--
<delete dir="bin" />
<delete dir="obj" />-->
<!--Create Version label file-->
<copy file="Version.txt" tofile="${publishdir}/Version-${CCNetLabel}.0.txt" overwrite="true"/>
<!--restore backup of bin,obj--><!--
<copy todir="bin" overwrite="true">
<fileset basedir="./bin_backup">
<include name="*.*"/>
</fileset>
</copy>
<copy todir="obj" overwrite="true">
<fileset basedir="./obj_backup">
<include name="*.*"/>
</fileset>
</copy>-->
<!--restore backup of config-->
<copy todir="." overwrite="true">
<fileset basedir="./config_backup">
<include name="*.config"/>
</fileset>
</copy>
</target>
</project>3. Unit Test Project default.build(包括Main以及Sub Projects)
Main Build (添加几个测试的Assembly Dll)
记得删除之前生成的Unit test result哦。。。。
**********************************
<?xml version="1.0" encoding="utf-8"?>
<project name="Project1Test" default="build">
<target name="build">
<nant target="build">
<buildfiles>
<include name="TestAppCode/default.build" />
<include name="TestBusinessLayer/default.build" />
</buildfiles>
</nant>
<call target="clean" />
<call target="test" />
</target>
<target name="clean" description="remove recent test results. ">
<delete failοnerrοr="false">
<!-- this prevents unit test results from appearing in cc console if the build fails -->
<fileset>
<include name="UnitTest-*" />
</fileset>
</delete>
</target>
<target name="test" description="runs the unit tests" >
<!-- test first Assembly. -->
<exec program="C:/Program Files/NUnit-Net-2.0 2.2.8/bin/nunit-console.exe" failοnerrοr="false" resultproperty="testresult.AppCodeTest.dll">
<arg value="TestAppCode/bin/TestAppCode.dll" />
<arg value="/xml=UnitTest-AppCodeTest.xml" />
</exec><!-- test second Assembly. -->
<exec program="C:/Program Files/NUnit-Net-2.0 2.2.8/bin/nunit-console.exe" failοnerrοr="false" resultproperty="testresult.BusinessLayerTest.dll">
<arg value="TestBusinessLayer/bin/TestBusinessLayer.dll" />
<arg value="/xml=UnitTest-BusinessLayerTest.xml" /> 。。。。文件名称有规则哦。。。。
</exec>
<!-- Check the results properties and fail if necessary -->Unit test的结果会影响到整个Project(DashBoard)的成功,初期暂时注释掉。等调试好所有Case之后,在打开。
<!--<fail message="Failures reported in unit tests." unless="${int::parse(testresult.AppCodeTest.dll)==0}" />
<fail message="Failures reported in unit tests." unless="${int::parse(testresult.BusinessLayerTest.dll)==0}" />-->
</target>
</project>**********************************
Sub Project Build File
<?xml version="1.0"?>
<project name="TestBusinessLayer" default="build">
<property name="basename" value="TestBusinessLayer"/>
<property name="debug" value="true"/>
<property name="uptodate" value="true"/><target name="clean">
<delete>
<fileset>
<include name="bin/${basename}.dll"/>
<include name="bin/${basename}.pdb"/>
</fileset>
</delete>
</target><target name="build">
<uptodate property="uptodate">
<sourcefiles>
<include name="*.cs"/>
</sourcefiles>
<targetfiles>
<include name="bin/${basename}.dll"/>
</targetfiles>
</uptodate>
<if test="${property::exists('CCNetLabel') and not uptodate}">
<asminfo output="FMAssemblyInfo.cs" language="CSharp">
<imports>
<import namespace="System" />
<import namespace="System.Reflection" />
<import namespace="System.Runtime.InteropServices" />
</imports>
<attributes>
<attribute type="ComVisibleAttribute" value="false" />
<attribute type="AssemblyVersionAttribute" value="${CCNetLabel}" />
<attribute type="AssemblyTitleAttribute" value="${basename}" />
<attribute type="AssemblyDescriptionAttribute" value="BusinessLayer Test" />
<attribute type="AssemblyCopyrightAttribute" value="Copyright © test" />
</attributes>
</asminfo>
</if>
<mkdir dir="bin" />
<csc target="library" output="bin/${basename}.dll" debug="${debug}">
<sources>
<include name="*.cs"/>
<include name="Properties/*.cs"/>
<exclude name="AssemblyInfo.cs"/>
<exclude name="FMAssemblyInfo.cs"/>
</sources>
<references>
<include name="C:/Program Files/NUnit-Net-2.0 2.2.8/bin/*.dll"/>
<include name="../../main/CommonDlls/*.dll"/>
<include name="../../main/BusinessLayer/bin/SummitBusinessLayer.dll"/>
</references>
</csc>
</target>
</project>