持续集成想必大家很多人都听说过,甚至都实践过,最近我又一次亲历了一次持续集成,现将我的经验分享给大家。关于持续集成的理论在本文概不涉及,本文的主要目的是实战CruiseControl.Net,用它来全面实现持续集成。
在配置ccnet.config时会用到一些小工具,一并附上:小工具下载
首先,我们来看看用CC.Net能为我们做哪些事情:
在开始之前,第一件事是配置我们的持续集成环境
获取SVN或TFS工具,TFS可通过安装VisualStudio获得,SVN可通过安装TortoiseSVN来获得。
安装和配置IIS,因为我们要使用Web页面查看持续集成的结果,所以需要配置IIS,安装CruiseControl.Net时会为我们创建一个名为ccnet的web应用程序。
从http://www.cruisecontrolnet.org/这个站点上现在我们需要的工具CruiseControl.Net并将其安装。
安装VisualStudio2013(当然也可选其它版本)。安装它的目的有两个,1. 使用了VSTest.Console.exe产生单元测试结果数据(UnitTest结果和测试覆盖率),2. 当编译不能通过时用它可以发现问题。
到此为止,集成环境已经OK,下面,我们来逐一来通过配置ccnet.config实现上述功能。
首先需要了解,持续集成的单位是以项目为单位,在ccnet.config文件里体现为Project,如下:
<project name="MyProject" description="demoproject showing a small config" queue="Q1"> <!--内部配置--> </project>
配置项目的源代地址,包括本地工作地址和源代码管理服务地址,对于使用TFS的源码管理器,向Project下添加如下配置:
<workingDirectory>E:\dailybuild</workingDirectory> <artifactDirectory>E:\dailybuild</artifactDirectory> <category>TestProject</category> <sourcecontrol type="vsts" autoGetSource="true" applyLabel="false"> <server>http://tfs1.TestProject.com:8080</server> <domain>TestProject.com</domain> <project>$/TestProject projects/Analysis and Design\Concierge\Prototype</project> <workingDirectory>E:\dailybuild\TestProject</workingDirectory> <cleanCopy>true</cleanCopy> </sourcecontrol>
对于使用svn源码管理器,可以使用以下配置:
<artifactDirectory>d:\svn\Log\MyProject</artifactDirectory> <sourcecontrol type="svn"> <executable>C:\Program Files\TortoiseSVN\bin\svn.exe</executable> <username>UserName</username> <password>******</password> <autoGetSource>true</autoGetSource> <trunkUrl>http://svnserver/trunk/MyProject</trunkUrl> <workingDirectory>d:\svn\trunk\MyProject</workingDirectory> </sourcecontrol>
2.实现自动Build,向Project节点下增加tasks节点,如下
<tasks> <msbuild> <executable>C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</executable> <buildArgs>/p:OutputPath=D:\BuildOutput</buildArgs> <workingDirectory>D:\svn\branch\project\</workingDirectory> <projectFile>mysolution.sln</projectFile> <targets>Build</targets> <timeout>9000</timeout> </msbuild> </tasks>
如果要生成单元测试报告和单元测试覆盖率,这里需要多下写功夫去配置。首先在创建Runsettings文件,如下:
CodeCoverage.runsettings的配置内容如下:
<?xml version="1.0" encoding="utf-8"?> <RunSettings> <RunConfiguration> <!-- Path relative to solution directory --> <ResultsDirectory>d:\svn\log\TestResults</ResultsDirectory> <!-- [x86] | x64 - You can also change it from menu Test, Test Settings, Default Processor Architecture --> <TargetPlatform>x86</TargetPlatform> <!-- Framework35 | [Framework40] | Framework45 --> <TargetFrameworkVersion>Framework40</TargetFrameworkVersion> </RunConfiguration> <DataCollectionRunSettings> <DataCollectors> <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <Configuration> <CodeCoverage> <!-- Match assembly file paths: --> <ModulePaths> <Include> <ModulePath>.*\.dll$</ModulePath> <ModulePath>.*\.exe$</ModulePath> </Include> <Exclude> <ModulePath>.*CPPUnitTestFramework.*</ModulePath> </Exclude> </ModulePaths> <!-- Match fully qualified names of functions: --> <!-- (Use "\." to delimit namespaces in C# or Visual Basic, "::" in C++.) --> <Functions> <Exclude> <Function>^Fabrikam\.UnitTest\..*</Function> <Function>^std::.*</Function> <Function>^ATL::.*</Function> <Function>.*::__GetTestMethodInfo.*</Function> <Function>^Microsoft::VisualStudio::CppCodeCoverageFramework::.*</Function> <Function>^Microsoft::VisualStudio::CppUnitTestFramework::.*</Function> </Exclude> </Functions> <!-- Match attributes on any code element: --> <Attributes> <Exclude> <!-- Don’t forget "Attribute" at the end of the name --> <Attribute>^System.Diagnostics.DebuggerHiddenAttribute$</Attribute> <Attribute>^System.Diagnostics.DebuggerNonUserCodeAttribute$</Attribute> <Attribute>^System.Runtime.CompilerServices.CompilerGeneratedAttribute$</Attribute> <Attribute>^System.CodeDom.Compiler.GeneratedCodeAttribute$</Attribute> <Attribute>^System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute$</Attribute> </Exclude> </Attributes> <!-- Match the path of the source files in which each method is defined: --> <Sources> <Exclude> <Source>.*\\atlmfc\\.*</Source> <Source>.*\\vctools\\.*</Source> <Source>.*\\public\\sdk\\.*</Source> <Source>.*\\microsoft sdks\\.*</Source> <Source>.*\\vc\\include\\.*</Source> </Exclude> </Sources> <!-- Match the company name property in the assembly: --> <CompanyNames> <Exclude> <CompanyName>.*microsoft.*</CompanyName> </Exclude> </CompanyNames> <!-- Match the public key token of a signed assembly: --> <PublicKeyTokens> <!-- Exclude Visual Studio extensions: --> <Exclude> <PublicKeyToken>^B77A5C561934E089$</PublicKeyToken> <PublicKeyToken>^B03F5F7F11D50A3A$</PublicKeyToken> <PublicKeyToken>^31BF3856AD364E35$</PublicKeyToken> <PublicKeyToken>^89845DCD8080CC91$</PublicKeyToken> <PublicKeyToken>^71E9BCE111E9429C$</PublicKeyToken> <PublicKeyToken>^8F50407C4E9E73B6$</PublicKeyToken> <PublicKeyToken>^E361AF139669C375$</PublicKeyToken> </Exclude> </PublicKeyTokens> <!-- We recommend you do not change the following values: --> <UseVerifiableInstrumentation>True</UseVerifiableInstrumentation> <AllowLowIntegrityProcesses>True</AllowLowIntegrityProcesses> <CollectFromChildProcesses>True</CollectFromChildProcesses> <CollectAspDotNet>False</CollectAspDotNet> </CodeCoverage> </Configuration> </DataCollector> </DataCollectors> </DataCollectionRunSettings> </RunSettings>
里面最重要的信息室配置了单元测试结果存放路径:<ResultsDirectory>d:\svn\log\TestResults</ResultsDirectory>,以便我们后来生成测试结果。
接着来配置ccnet.config,以执行单元测试
<!--删除上次单元测试结果--> <exec> <executable>D:\svn\tool\delfile.bat</executable> <buildArgs>D:\svn\Log\TestResultsReal\mstest-results.trx</buildArgs> <buildTimeoutSeconds>300</buildTimeoutSeconds> <successExitCodes>-1,0</successExitCodes> </exec> <exec> <executable>D:\svn\tool\delfile.bat</executable> <buildArgs>D:\svn\Log\TestResultsReal\mstest-coverage.xml</buildArgs> <buildTimeoutSeconds>300</buildTimeoutSeconds> <successExitCodes>-1,0</successExitCodes> </exec> <!--执行单元测试--> <exec> <executable>C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\VSTest.Console.exe</executable> <baseDirectory>D:\svn\UnitTest.dll所在的文件目录</baseDirectory> <buildArgs>UnitTest.dll /Enablecodecoverage /Settings:D:\svn\tool\CodeCoverage.runsettings /logger:trx</buildArgs> <buildTimeoutSeconds>300</buildTimeoutSeconds> </exec> <!--生成测试覆盖率--> <exec> <executable> <!--删除上次单元测试结果--> <exec> <executable>D:\svn\tool\delfile.bat</executable> <buildArgs>D:\svn\Log\TestResultsReal\mstest-results.trx</buildArgs> <buildTimeoutSeconds>300</buildTimeoutSeconds> <successExitCodes>-1,0</successExitCodes> </exec> <exec> <executable>D:\svn\tool\delfile.bat</executable> <buildArgs>D:\svn\Log\TestResultsReal\mstest-coverage.xml</buildArgs> <buildTimeoutSeconds>300</buildTimeoutSeconds> <successExitCodes>-1,0</successExitCodes> </exec> <!--执行单元测试--> <exec> <executable>C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\VSTest.Console.exe</executable> <baseDirectory>D:\svn\UnitTest.dll所在的文件目录</baseDirectory> <buildArgs>UnitTest.dll /Enablecodecoverage /Settings:D:\svn\tool\CodeCoverage.runsettings /logger:trx</buildArgs> <buildTimeoutSeconds>300</buildTimeoutSeconds> </exec> <!--生成测试覆盖率--> <exec> <executable>D:\svn\tool\coverage\Auto.Dealer.UnitTest.Tool.exe</executable> <buildArgs>D:\svn\log\TestResults\ D:\svn\log\TestResultsReal\mstest-results.trx D:\svn\log\TestResultsReal\mstest-coverage.xml</buildArgs> <buildTimeoutSeconds>300</buildTimeoutSeconds> <successExitCodes>-1,0</successExitCodes> </exec> <!--合并xml文件--> <merge> <files> <file>D:\svn\Log\TestResultsReal\mstest-results.trx</file> <!--这个文件是用于合并UnitTest result--> </files> </merge> <merge> <files> <file>D:\svn\Log\TestResultsReal\mstest-coverage.xml</file> <!--这个文件是用于合并UnitTest result--> </files> </merge>D:\svn\tool\coverage\Auto.Dealer.UnitTest.Tool.exe</executable> <buildArgs>D:\svn\log\TestResults\ D:\svn\log\TestResultsReal\mstest-results.trx D:\svn\log\TestResultsReal\mstest-coverage.xml</buildArgs> <buildTimeoutSeconds>300</buildTimeoutSeconds> <successExitCodes>-1,0</successExitCodes> </exec> <!--合并xml文件--> <merge> <files> <file>D:\svn\Log\TestResultsReal\mstest-results.trx</file> <!--这个文件是用于合并UnitTest result--> </files> </merge> <merge> <files> <file>D:\svn\Log\TestResultsReal\mstest-coverage.xml</file> <!--这个文件是用于合并UnitTest result--> </files> </merge>
这里,想必大家已经注意到有两处删除操作,因为持续集成式一个不断重复的过程,如果不删除原来的测试结果就会发生错误。另外,用到的一个工具(这个工具里的代码很简单,一并提供出来如下),用来将trx文件转化为xml文件,即单元测试覆盖率结果。最后将它们一起合并到CruiseControl的执行日志里。
生成单元测试覆盖率代码如下:
class Program { static void Main(string[] args) { string dirName = args[0]; string trxUutPutFileName = args[1]; string coverageoutPutFileName = args[2]; if (Directory.Exists(dirName)) { DirectoryInfo dirc = new DirectoryInfo(dirName); foreach (FileInfo file in dirc.GetFiles("*.trx")) { file.CopyTo(trxUutPutFileName, true); break; } foreach (FileInfo file in dirc.GetFiles("*.coverage", SearchOption.AllDirectories)) { ConvertToXML(file.FullName, coverageoutPutFileName); break; } dirc.Delete(true); } else { Console.WriteLine("没找到目录:"+dirName); } } public static void ConvertToXML(string coverageFile, string outputFile) { using( CoverageInfo coverageInfo = CoverageInfo.CreateFromFile(coverageFile)) { using (CoverageDS ds = coverageInfo.BuildDataSet()) { ds.ExportXml(outputFile); } } } }
<!--发布到站点--> <buildpublisher> <sourceDir>d:\svn\_PublishedWebsites\MyWeb</sourceDir> <publishDir>\\IP地址\website\</publishDir> <useLabelSubDirectory>false</useLabelSubDirectory> </buildpublisher>
如果有自动化测试框架,则可以考虑部署完毕后自动触发执行自动化测试,由于自动化测试框架可能会有很大差异,这里就不在给出配置,总的来说,使用<exec></exec>可以很灵活地实现我们的需求。
无论持续集成执行成功,还是失败,都可以配置相应的邮件接收人员。邮件配置要放到<publishers></publishers>。这样,邮件发送的失败就不会阻塞持续集成。
<publishers> <email mailport="25" includeDetails="TRUE" mailhostUsername="[email protected]" mailhostPassword="******" useSSL="FALSE"> <from>[email protected]</from> <mailhost>smtp.sina.net</mailhost> <users> <user name="张三" group="developers" address="[email protected]" /> <user name="李四" group="developers" address="[email protected]" /> <user name="王五" group="developers" address="[email protected]" /> </users> <groups> <group name="developers"> <notifications> <notificationType>Failed</notificationType> <notificationType>Fixed</notificationType> </notifications> </group> <group name="buildmaster"> <notifications> <notificationType>Always</notificationType> </notifications> </group> </groups> <converters> <regexConverter find="$" replace="@sina.com" /> </converters> <modifierNotificationTypes> <NotificationType>Failed</NotificationType> <NotificationType>Fixed</NotificationType> </modifierNotificationTypes> <subjectSettings> <subject buildResult="StillBroken" value="Build is still broken for {CCNetProject}" /> </subjectSettings> </email> <statistics /> <xmllogger /> </publishers>
打开http://CruiseControl所在机的IP/ccnet/,可以看到如下连接,下载并安装。就可以监控制定的项目了。
最后,做了以上所有的事情以后,在http://CruiseControl所在机的IP/ccnet/这个站点上并不能看到我们所有的持续集成结构,CruiseControl为我们提供了一些Plugins,即一些xsl文件,使用它们就可以显示我们想要的结果了。这些xsl文件的地址为:CruiseControl的安装路径\CruiseControl.NET\webdashboard\xsl。我们需要修改一下dashboard.config这个文件,我向其中添加了如下内容:
<buildPlugins> <buildReportBuildPlugin> <xslFileNames> <xslFile>xsl\header.xsl</xslFile> <xslFile>xsl\modifications.xsl</xslFile> <xslFile>xsl\unittests.xsl</xslFile> <xslFile>xsl\MsTestSummary2008.xsl</xslFile> <xslFile>xsl\compile-msbuild.xsl</xslFile> <xslFile>xsl\SimianSummary.xsl</xslFile> <xslFile>xsl\MsTestSummary2010.xsl</xslFile> <xslFile>xsl\MsTestSummary.xsl</xslFile> <xslFile>xsl\MsTestReport2010.xsl</xslFile> <xslFile>xsl\MsTestCover2010.xsl</xslFile> </xslFileNames> </buildReportBuildPlugin> </buildPlugins>
这样,重启ccnet站点后,我们的测试结果也会被格式化地显示出来。
以每日构建为例,需要在<triggers></triggers>里配置如下结果:
<scheduleTrigger time="23:30" buildCondition="ForceBuild" name="Scheduled"> <weekDays> <weekDay>Monday</weekDay> <weekDay>Tuesday</weekDay> <weekDay>Wednesday</weekDay> <weekDay>Thursday</weekDay> <weekDay>Friday</weekDay> </weekDays> </scheduleTrigger>
至此,我们的持续集成也基本上可以告一段落了。可以使用它来进行一些自动化的工作了。