單元測試實踐 1.測試框架選擇Unitils 為什么選擇Untils作為本次項目單元測試框架呢? Unitils的優點和介紹網上都比較詳細:http://www.unitils.org/summary.html;它的主要模塊有: · DatabaseModule: 測試數據庫維護和連接池 · DbUnitModule: 使用DBunit進行測試數據維護 · HibernateModule: Hibernate配置支持和自動的數據庫mapping檢查 · MockModule: 支持使用Unitils的mock框架進行mock創建 · EasyMockModule: 支持使用EasyMock的mock框架進行mock創建 · InjectModule: 支持注入mock對象到其他對象中 · SpringModule: 支持載入Spring配置文件、檢索或注入Spring Beans 本次項目并沒有全部用到它的所有特性,其中我使用到的優秀特性和功能有: · 為Spring集成的單元測試提供很好的解決方案 · 數據庫相關測試的數據準備,事務回滾 · 簡單方便的Assert工具 2.在測試中使用Unitils 方法一. 繼承UnitilsJUnit4 方法二. 在測試類上添加annotation:@RunWith(UnitilsJUnit4TestClassRunner.class) 方法三. 拷貝UnitilsJUnit3(UnitilsJUnit4)的代碼生成一個MyUnitilsJunit3,然后測試類繼承這個類。這種方法的好處是MyUnitilsJunit3可以隨意繼承一個Junit3 TestCase 的子類,比如: AbstractDependencyInjectionSpringContextTests,從而可以更加方便的加入自己需要的功能。 3.使用Unitils進行Spring集成的單元測試 在基類中設置公用的Spring 配置 @SpringApplicationContext( {"/bean/profile/base-beans.xml"}) public class DAOTestBase extends UnitilsJUnit4{ } 子類中特殊化的Spring配置 public class UserDAOTest extends DAOTestBase{ @SpringApplicationContext( {"/bean/profile/base-beans.xml","/bean/profile/extra-beans.xml"}) protected ApplicationContext applicationContext; @SpringBean("userDAO") private UserDAO userDAO; @SpringBeanByName private UserDAO userDAO; @SpringBeanByType private UserDAO userDAO; } 4.使用DBunit進行數據庫相關的測試 (1). 在unitils.properties當中進行配置 database.driverClassName=com.mysql.jdbc.Driver database.url=jdbc:mysql://192.168.205.62:3310/pro_general?characterEncoding=UTF-8 database.userName=profile database.password=profile database.schemaNames=pro_general database.dialect=mysql 設置數據載入策略為先刪除再插入。常用的載入策略有CleanInsertLoadStrategy;InsertLoadStrategy;RefreshLoadStrategy;UpdateLoadStrategy;顧名思義,這些策略不難理解。 DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.impl.DeleteInsertLoadStrategy 數據集的格式支持多種,常用的有xml和excel,本項目中使用excel的xls文件(不是xlsx)。畢竟excel的編輯更加方便。下面的配置指定默認數據集解析方式: DbUnitModule.DataSet.factory.default=org.unitils.dbunit.datasetfactory.impl.XlsDataSetFactory (2). 在測試類中指定數據加載 @DataSet public class TagTest extends DAOTestBase { @ExpectedDataSet public void testUpdate(){ } } Annotation @DataSet指明該類下的所有測試方法執行前都需要進行數據準備。DataSet中可以指明數據文件的具體路徑和文件名,如果沒有指定,默認在執行測試方法前先找${ClassName.methodName}的數據文件,再找${ClassName}的數據文件(文件格式為前面設置過的數據集格式)。 @ExpectedDataSet用于檢查執行結果是否和預期一致。預期數據集文件和前面的數據準備文件查找過程類似只是文件名后面多了個“-result”。 5.使用Unitils事務保障數據庫相關測試的一致性 @Transactional(TransactionMode.ROLLBACK) 在測試類或方法上添加Transactional Anotation,用于指定事務執行方式。這里提一下Unitils的一個bug,使用Transactional Anotation時測試類必須繼承UnitilsJUnit4,用@RunWith的方式則不行。 測試覆蓋率 之前的項目采用Clover來進行測試覆蓋率的測算,用著還不錯,挺好用的。但是由于Clover是非開源的,要給錢,免費的licence過期了,只能換一個開源的了。這次選用了cobertura,一個Jcoverage的分支,也有maven的插件。 在maven主pom進行配置: <build> <plugins> …… <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.4-SNAPSHOT</version> <configuration> <formats> <format>html</format> <format>xml</format> </formats> <instrumentation> <!--<ignore>.*</ignore> --> <excludes> <exclude>**/*Test.class</exclude> <exclude>**/Abstract*TestCase.class</exclude> <exclude>**/*Constants.class</exclude> <exclude>**/*interface/*.class</exclude> <exclude>**/*domain/*.class</exclude> <exclude>**/*dataobject/*.class</exclude> <exclude>**/web/**/*.class</exclude> </excludes> </instrumentation> </configuration> </plugin> ...... </plugins> </build> 執行mvn cobertura:cobertura命令即可得出測試覆蓋率報表,其中主要包括Line coverage,branch coverage。得出的報表是以子項目為單位的。由于cobertura在maven插件中并未提供merge的功能,所有只依靠cobertura- maven-plugin無法得出整個項目的測試覆蓋率。 但是可以依靠其他手段獲得整個項目的。具體實施方法如下: 1. 下載coberturahttp://cobertura.sourceforge.net/download.html.(如果不想自己打包,可以直接下載bin文件而不用src.如果對其源碼敢興趣可以下載src,cobertura是基于ant構建的) 2. 執行 mvn cobertura:cobertura 命令。執行完成后在各個子項目的target/cobertura里面會生成cobertura.ser文件。但是主pom對應的target/cobertura目錄下并沒有cobertura.ser 3.將子項目的cobertura.ser進行merge,生成整個項目的cobertura.ser文件。 ../cobertura-1.9.3/cobertura-merge.sh --datafile ./target/cobertura/cobertura.ser ./profile-ao/target/cobertura/cobertura.ser ./profile-core/target/cobertura/cobertura.ser ./profile-dal/target/cobertura/cobertura.ser ./profile-client/target/cobertura/cobertura.ser ./profile-common/target/cobertura/cobertura.ser 執行完上面命令后,在主pom對應的target/cobertura目錄下會生成一個整個項目的cobertura.ser文件 4. 再執行mvn cobertura:cobertura命令。執行完成后,整個項目的測試覆蓋率就生成了。 持續集成+測試覆蓋率 測試覆蓋率作為持續集成不可或缺的一個質量指標,將測試覆蓋率和持續集成進行整合當然也就必不可少了。本項目持續集成平臺采用CruiseControl 1.測試覆蓋率結果作為持續集成是否通過的指標 (1)在maven中對cobertura的 check屬性進行配置 <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <configuration> <check> <branchRate>85</branchRate> <lineRate>85</lineRate> <haltOnFailure>true</haltOnFailure> <totalBranchRate>85</totalBranchRate> <totalLineRate>85</totalLineRate> <packageLineRate>85</packageLineRate> <packageBranchRate>85</packageBranchRate> <regexes> <regex> <pattern>com.taobao.memberprofile.core.*</pattern> <branchRate>90</branchRate> <lineRate>80</lineRate> </regex> <regex> <pattern> com.taobao.memberprofile.dal.*</pattern> <branchRate>40</branchRate> <lineRate>30</lineRate> </regex> </regexes> </check> </configuration> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> (2)在持續集成的時候加入cobertura的check過程 修改CruiseControl的配置config.xml <schedule Interval="60"> <maven2 MvnHome="/opt/taobao/install/apache-maven-2.1.0" PomFile="${checkoutdir}/${project.name}/pom.xml" Goal="install cobertura:check cobertura:cobertura" /> </schedule> 2.將測試覆蓋率結果展現集成到持續集成結果展現平臺中 (1)將測試覆蓋率報表放到artifacts目錄里面。因為原來目錄里面是沒辦法直接訪問到的。 修改CruiseControl的配置config.xml <artifactspublisher dir="${checkoutdir}/${project.name}/target/site/cobertura" subdirectory="cobertura" dest="artifacts/${project.name}/"> (2)集成到CruiseControl老的展示頁面中(cruisecontrol路徑下的) 首先,修改main.jsp,添加cobertura的tab: <cruisecontrol:tabsheet> <cruisecontrol:tab name="buildResults" label="Build Results" > <%@ include file="buildresults.jsp" %> </cruisecontrol:tab> <cruisecontrol:tab name="TestCoverage" label="Test Coverage" > <%@ include file="cobertura.jsp" %> </cruisecontrol:tab> <cruisecontrol:tab name="testResults" label="Test Results" > <%@ include file="testdetails.jsp" %> </cruisecontrol:tab> <cruisecontrol:tab name="metrics" label="Metrics" > <%@ include file="metrics.jsp" %> </cruisecontrol:tab> </cruisecontrol:tabsheet> 然后,添加cobertura.jsp,其內容為: <%@ taglib uri="/WEB-INF/cruisecontrol-jsp11.tld" prefix="cruisecontrol"%> <cruisecontrol:xsl xslFile="/xsl/header.xsl"/> <p> <cruisecontrol:artifactsLink> <iframe name="CoberturaFrame" id="cloverFrame" style="width:100%; height:600;" marginheight="10" frameborder="0" marginwidth="10" src="<%= artifacts_url %>/cobertura/index.html"> </iframe> </cruisecontrol:artifactsLink> </p> 其效果為:
(3)集成到新的展示頁面中(dashboard路徑下的) 首先修改webapps/dashboard/WEB-INF/vm/build_detail/build_detail_pass ed.vm: #parse("build_detail/build_detail_partial_header.vm") <div class="build_detail_container"> <div class="sub_tab_container_menu"> <ul> <li class="current_tab tab_toggle"><a><span>Artifacts</span></a></li> <li class="tab_toggle"><a><span>Modifications</span></a></li> <li class="tab_toggle"><a><span>Build Log</span></a></li> <li class="tab_toggle"><a><span>Tests</span></a></li> <li class="tab_toggle"><a><span>Test Coverage</span></a></li> #parse('build_detail/build_detail_partial_widgets_tab.vm') </ul> </div> <div class="sub_tab_container_content"> #set($artifacts_extra_attrs="") #parse("build_detail/build_detail_partial_artifacts.vm") #set($modification_extra_attrs="style='display:none'") #parse("build_detail/build_detail_partial_modification.vm") #set($log_extra_attrs="style='display:none'") #parse("build_detail/build_detail_partial_log.vm") #set($tests_extra_attrs="style='display:none'") #parse("build_detail/build_detail_partial_tests.vm") #set($coverage_extra_attrs="style='display:none'") #parse("build_detail/build_detail_partial_testcoverage.vm") #parse('build_detail/build_detail_partial_widgets_content.vm') </div> </div> 然后,在相同目錄下新建build_detail_partial_testcoverage.vm,內容為: <div id="test_coverage" class="widget" $coverage_extra_attrs> #if( $buildCmd.build.artifactFiles.size() == 0) <p>No Test Coverage Report found.</p> #end #foreach ($artifactFile in $buildCmd.build.artifactFiles) #if($artifactFile.name.equals("cobertura")) <iframe src="$url/index.html" style="width:100%;" height=600 marginheight="10" frameborder="0" marginwidth="10"> </iframe> #end #end </div> 其效果為:
|
原文轉自:http://www.uml.org.cn/Test/201204111.asp