持续集成(Continuous Integration)可能对不熟悉自动化测试的部分人来说是一个新鲜词,然而它在软件开发,特别是大型的开发任务过程中,正在发挥越来越大的作用。
软件开发为了保证代码的稳定和可用,测试是必不可少的,没有哪个公司和个人敢说自己的代码100%没有BUG。在软件测试这一块,很多企业是招专门的测试工程师进行人工测试。人工测试有自己的特有的优点,但自动化测试也有自己的好的一面。
1)节约测试的人力成本
2)测试点全面,可能人有时候会忘记要对哪些点做测试,但机器不会忘记。
3)如果代码有BUG,它可以即时提供精确的日志信息和错误报告邮件发给开发者,实现错误的即时,精准定位。
4)有的测试可能人工没法完成,如对网站进行高并发访问的压力测试。
CruiseControl :简称 CC ,持续集成工具,主要提供了基于版本管理工具 ( 如 CVS、VSS、SVN) 感知变化或每天定时的持续集成,并提供持续集成报告、 Email 、 Jabber 等等方式通知相关负责人,其要求是需要进行日构建的项目已编写好全自动的项目编译脚本 ( 可基于 Maven 或 Ant) 。
我这段时间一直在做CC的搭建工作,在不同的平台下(windows 32, windows 64, linux32, linux64 ),现在做一个小结。
第一步:安装CC
我现在使用的CC版本是2.8.3,可能在这之前你需要安装jdk, svn client, ant.
第二步:配置CC
启动CC的入口程序windows下是cruisecontrol.bat,linux下是cruisecontrol.sh
CC 启动的时候会将配置文件以参数的方式传递给它,也可以使用默认的配置文件文件名config.xml,就像ant 命令的默认配置文件是执行命令的目录下的build.xml一样。
CC config.xml Sample:
<?xml version="1.0" encoding="utf-8"?>
<cruisecontrol>
<property name="ant.home" value="C:/ant" />
<property name="CCDIR" value="C:/nightly/cc-2.8.3" />
<property name="CC.root" value="C:/nightly/QAFrame/cruiseNightly" />
<property name="CRUISE_WORKSPACE" value="${CC.root}/splitpoint-win32" />
<property name="CC.configs" value="${CRUISE_WORKSPACE}/configs" />
<system>
<configuration>
<threads count="2" />
</configuration>
</system>
<project name="splitpoint-win32" forceOnly="false" requiremodification="false" buildafterfailed="false">
<property name="WORKSPACE" value="${CC.root}" />
<property name="MAIL_FROM" value="win32 Cruise Build - Federal Test:" />
<listeners>
<currentbuildstatuslistener file="${CRUISE_WORKSPACE}/logs/${project.name}/status.txt" />
<lockfilelistener lockfile="${CRUISE_WORKSPACE}/logs/splitpoint-win32.lock" projectname="${project.name}" />
</listeners>
<modificationset quietperiod="30">
<svn localWorkingCopy="${WORKSPACE}" />
</modificationset>
<schedule interval="20">
<!-- 常用的builder: exec, ant -->
<exec time="0912"
command="${CRUISE_WORKSPACE}/build.bat"
workingdir="${WORKSPACE}"
errorstr="run build.bat error"
/>
</schedule>
<log dir="${CRUISE_WORKSPACE}/logs/${project.name}"><!-- CC 的日志目录-->
<!-- 要集成到CC日志中的不同工程的日志目录,这些工程的日志文件是xml
格式的,CC本身实现了对 Junit 的支持,包括Junit测试的日志文件生成和
Web console (http://localhost:8080/cruisecontrol/) 的测试结果显示;如果你 在CC中运行Junit的测试,可以使用CC自带的Junit日志文件,否则就需要自己 写日志文件和xsl来解析
-->
<merge dir="${WORKSPACE}/UnitTestApp/report" />
<merge dir="${WORKSPACE}/WSScript/report" />
<merge dir="${WORKSPACE}/Webide/report" />
</log>
<publishers>
<htmlemail mailhost="mailhost.pb.intel.com"
returnaddress="SplitPoint-QA@intel.com"
returnname="Win32 Federal Test ( Nightly Install )"
subjectprefix="Federal Test (Checkin Build) :"
skipusers="false"
spamwhilebroken="true" <!-- send mail if CC build failed --> css="${CC.root}/cc-bin/windows/webapps/cruisecontrol/css/cruisecontrol.css"
xsldir="${CC.root}/cc-bin/windows/webapps/cruisecontrol/xsl"
charset="utf-8"
logdir="${CRUISE_WORKSPACE}/logs/${project.name}">
<parameter name="noCCtableWorkaround" value="yes" />
<always address="yousuf007@163.com" />
<failure address="yousuf007@163.com" reportWhenFixed="true" />
</htmlemail>
</publishers>
</project>
</cruisecontrol>
当不同的项目均需要Junit测试时,可以把它们配置在同一个build.xml 中,但它们的程序依赖,目录结构,业务功能可能不一样,而且testcase数据可能很多,所以有必要将它们的Junit Test task 放在各自的ant task 里面( 即在build.xml 配置batchtest )。
但由于生成Junit 的日志文件(.xml 格式)是CC的内置支持功能,在没有找到CC的这部分代码的情况下,想到了另外一个解决方案。对CC生成的Junit 日志文件进行修改,给Junit 日志文件的root node <testsuites> 增加一个name属性,这样就可以在xsl 中根据name 来区分不同的Junit 测试集 。
Linux 下可以用 shell 中的 sed 命令来修改
Windows下使用BAT COMMAND,下面附上BAT的实现代码:
@echo off
set junit_log_file=%1
shift
set testsuites_name=%1
shift
echo [echo] modify Junit Log file to catalog Junit Test set in mail
type nul > junit.xml
for /f "tokens=* delims= " %%a in ('type %junit_log_file%') do if "%%a" EQU "<testsuites>" (
echo ^<testsuites name="%testsuites_name%"^> >> junit.xml
) else (
echo %%a >> junit.xml
)
)
if exist junit.xml (
del /F %junit_log_file%
move junit.xml %junit_log_file%
)
相应的生成测试汇总信息的xsl file也需要更改:
File : summary-test.xsl
<xsl:template match="/">
<xsl:apply-templates select="/cruisecontrol" mode="summary" />
</xsl:template>
<xsl:template match="/cruisecontrol" mode="summary">
<html>
<head>
<title>Unit Test Results: Summary</title>
</head>
<body>
<xsl:variable name="test_report_url" select="/cruisecontrol/host/TestResultUrl"/>
<xsl:variable name="wscript_test_flag" select="count(/cruisecontrol/WscriptTest)" />
<xsl:variable name="junit_test_flag" select="count(/cruisecontrol/testsuites)" />
<xsl:variable name="test_flag" select="( $wscript_test_flag + $junit_test_flag )"/>
<xsl:if test="$test_flag > 0">
<h2>Test Summary</h2>
<!--
get test result for wscript test
-->
<xsl:variable name="fail" select="/cruisecontrol/WscriptTest/fail/text()"/>
<xsl:variable name="total" select="/cruisecontrol/WscriptTest/total"/>
<xsl:variable name="successRate2" select="($total - $fail) div $total"/>
<table class="detail" border="0" cellpadding="5" cellspacing="2" width="95%">
<tr valign="top" class="junit-test-info">
<th>Type</th>
<th>Tests</th>
<th>Failures</th>
<th>Errors</th>
<th>Success rate</th>
<th>Time</th>
</tr>
<!--
display Junit Test summary
-->
<xsl:if test="$junit_test_flag > 0">
<xsl:call-template name="junit_testsuites">
<xsl:with-param name="test_report_url" select="$test_report_url"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="$wscript_test_flag > 0">
<tr valign="top">
<xsl:attribute name="class">
<xsl:choose>
<xsl:when test="$fail > 0">Error</xsl:when>
<xsl:otherwise>Pass</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<td class="junit-test-info" style="color:black">Wscript</td>
<td>
<a title="Display all tests">
<xsl:attribute name="href">
<xsl:value-of select="$test_report_url"/>
</xsl:attribute>
<xsl:value-of select="$total"/>
</a>
</td>
<td><a title="Display all tests">
<xsl:attribute name="href">
<xsl:value-of select="$test_report_url"/>
</xsl:attribute>
<xsl:value-of select="$fail"/></a></td>
<td>0</td>
<td>
<xsl:call-template name="display-percent">
<xsl:with-param name="value" select="$successRate2"/>
</xsl:call-template>
</td>
<td>
<xsl:value-of select="/cruisecontrol/WscriptTest/costtime" />
</td>
</tr>
</xsl:if>
</table>
<table border="0" width="95%">
<tr>
<td style="text-align: justify;">
Note: <em>failures</em> are anticipated and checked for with assertions while <em>errors</em> are unanticipated.
</td>
</tr>
</table>
</xsl:if>
</body></html>
</xsl:template>
<xsl:template name="display-percent">
<xsl:param name="value"/>
<xsl:value-of select="format-number($value,'0.00%')"/>
</xsl:template>
<xsl:template name="display-time">
<xsl:param name="value"/>
<xsl:value-of select="format-number($value,'0.000')"/>
</xsl:template>
<!-- class header -->
<xsl:template name="testsuite.test.header">
<tr valign="top" class="junit-test-info">
<th width="80%">Name</th>
<th>Tests</th>
<th>Errors</th>
<th>Failures</th>
<th nowrap="nowrap">Time(s)</th>
<th nowrap="nowrap">Time Stamp</th>
<th>Host</th>
</tr>
</xsl:template>
<!--
generate summary junit test such as webide, splat.war
-->
<xsl:template match="/cruisecontrol" mode="junit">
<xsl:param name="test_report_url" />
<xsl:for-each select="testsuites">
<!--
get test result for junit test
-->
<xsl:variable name="testCount" select="sum(testsuite/@tests)"/>
<xsl:variable name="errorCount" select="sum(testsuite/@errors)"/>
<xsl:variable name="failureCount" select="sum(testsuite/@failures)"/>
<xsl:variable name="timeCount" select="sum(testsuite/@time)"/>
<xsl:variable name="successRate1" select="($testCount - $failureCount - $errorCount) div $testCount"/>
<tr valign="top">
<xsl:attribute name="class">
<xsl:choose>
<xsl:when test="$errorCount > 0">Error</xsl:when>
<xsl:when test="$failureCount > 0">Failure</xsl:when>
<xsl:otherwise>Pass</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<td class="junit-test-info" style="color:black">
<xsl:choose>
<xsl:when test="./@name">
<xsl:value-of select="./@name"/>
</xsl:when>
<xsl:otherwise>Junit Test</xsl:otherwise>
</xsl:choose>
</td>
<td>
<a title="Display all tests">
<xsl:attribute name="href">
<xsl:value-of select="$test_report_url"/>
</xsl:attribute>
<xsl:value-of select="$testCount"/>
</a>
</td>
<td>
<a title="Display all tests">
<xsl:attribute name="href">
<xsl:value-of select="$test_report_url"/>
</xsl:attribute>
<xsl:value-of select="$failureCount"/>
</a>
</td>
<td>
<a title="Display all tests">
<xsl:attribute name="href">
<xsl:value-of select="$test_report_url"/>
</xsl:attribute>
<xsl:value-of select="$errorCount"/>
</a>
</td>
<td>
<xsl:call-template name="display-percent">
<xsl:with-param name="value" select="$successRate1"/>
</xsl:call-template>
</td>
<td>
<xsl:call-template name="display-time">
<xsl:with-param name="value" select="$timeCount"/>
</xsl:call-template>
</td>
</tr>
</xsl:for-each>
</xsl:template>
<xsl:template name="junit_testsuites">
<xsl:param name="test_report_url"/>
<xsl:apply-templates select="/cruisecontrol" mode="junit">
<xsl:sort select="@name"/>
<xsl:with-param name="test_report_url" select="$test_report_url"/>
</xsl:apply-templates>
</xsl:template>
最后邮件的内容如上所示。Test Summary 中的链接指向CC的Test Results页面http://10.239.47.233:8080/cruisecontrol/buildresults/splitpoint-win32?tab=testResults);
因为Wscript 不是Junit 测试,所以我们需要修改CC的xsl/testdetails.xsl 文件以显示Wscript测试结果。
当然我们有时并不想看所有测试的结果明细,如我只想看Wscript 测试中的没有通过的testcase Wscript001, 所以我写了一个 JSP 页面来对显示单个的testcase 信息,XML源树本来想用CC的日志文件,但发现CC代码中取当前日志文件的文件名是protect方法,不能直接拿来用。于是想到使用Wscript本身的日志文件,在 Test task 结束后将Wscript本身的日志文件移到 logDir 目录下,然后在 JSP 页面中 load 这个XML源树,即可检出所需的testcase日志信息,下附这个JSP的页面代码,这里面的难点在于如何从 javascript 传参到 xsl (传参方法IE和Firefox不一样)。
File : mail-link.jsp
<html>
<body>
<%
String wscript_logfile= request.getParameter("log");
String error_id = request.getParameter("error_id");
String project = request.getParameter("project");
String servername = request.getServerName();
%>
<div id="popularTags" name="<%=error_id%>"></div>
<div id="wscript_log" name="<%=wscript_logfile%>"></div>
<div id="project" name="<%=project%>"></div>
<div id="servername" name="<%=servername%>"></div>
<script language="javascript" type="text/javascript">
function XMLDocLoad(fname)
{
var xmlDoc;
if (window.ActiveXObject){
// code for IE
//xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
xmlDoc=new ActiveXObject("MSXML2.FreeThreadedDomDocument");
xmlDoc.async=false;
xmlDoc.load(fname);
return(xmlDoc);
}
else if(document.implementation && document.implementation.createDocument){
// code for Mozilla, Firefox, Opera, etc.
xmlDoc=document.implementation.createDocument("","",null);
xmlDoc.async=false;
xmlDoc.load(fname);
return(xmlDoc);
}
else{
alert('Your browser cannot handle this script');
}
}
function ShowPopularTags()
{
xml=XMLDocLoad("http://" + document.getElementById("servername").getAttribute("name") + ":8080/cruisecontrol/logs/" +document.getElementById("project").getAttribute("name")+ "/" + document.getElementById("wscript_log").getAttribute("name") );
xsl=XMLDocLoad("xsl/mail-link.xsl");
if (window.ActiveXObject){
// code for IE don't pass parameter to XSL file
//ex=xml.transformNode(xsl);
//document.getElementById("popularTags").innerHTML=ex;
var xslt = new ActiveXObject("MSXML2.XSLTemplate.3.0");
//alert(xsl.documentElement.xml);
xslt.stylesheet = xsl;
xsltProcessor = xslt.createProcessor();
xsltProcessor.input=xml ;
xsltProcessor.addParameter("test_id",document.getElementById("popularTags").getAttribute("name") );
xsltProcessor.transform();
document.getElementById("popularTags").innerHTML=xsltProcessor.output;
}
else if (document.implementation && document.implementation.createDocument){
// code for Mozilla, Firefox, Opera, etc.
xsltProcessor=new XSLTProcessor();
xsltProcessor.importStylesheet(xsl);
xsltProcessor.setParameter(null,"test_id", document.getElementById("popularTags").getAttribute("name"));
resultDocument = xsltProcessor.transformToFragment(xml,document);
document.getElementById("popularTags").innerHTML = "";
document.getElementById("popularTags").appendChild(resultDocument);
//var ihtml = document.getElementById("popularTags").innerHTML;
//document.getElementById("popularTags").innerHTML = ihtml;
}
}
ShowPopularTags();
</script>
</body>
</html>