许晓斌_Maven实战(五)——自动化Web应用集成测试
from:http://www.infoq.com/cn/news/2011/03/xxb-maven-5-integration-test
自動(dòng)化集成測(cè)試的角色
本專欄的上一篇文章講述了Maven與持續(xù)集成的一些關(guān)系及具體實(shí)踐,我們都知道,自動(dòng)化測(cè)試是持續(xù)集成必不可少的一部分,基本上,沒(méi)有自動(dòng)化測(cè)試的持續(xù)集成,都很難稱之為真正的持續(xù)集成。我們希望持續(xù)集成能夠盡早的暴露問(wèn)題,但這遠(yuǎn)非配置一個(gè) Hudson/Jenkins服務(wù)器那么簡(jiǎn)單,只有真正用心編寫(xiě)了較為完整的測(cè)試用例,并一直維護(hù)它們,持續(xù)集成才能孜孜不倦地運(yùn)行測(cè)試并第一時(shí)間報(bào)告問(wèn)題。
自動(dòng)化測(cè)試這個(gè)話題很大,本文不想爭(zhēng)論測(cè)試先行還是后行,這里強(qiáng)調(diào)的是測(cè)試的自動(dòng)化,并基于具體的技術(shù)(Maven、 JUnit、Jetty等)來(lái)介紹一種切實(shí)可行的自動(dòng)化Web應(yīng)用集成測(cè)試方案。當(dāng)然,自動(dòng)化測(cè)試還包括單元測(cè)試、驗(yàn)收測(cè)試、性能測(cè)試等,在不同的場(chǎng)景下,它們都能為軟件開(kāi)發(fā)帶來(lái)極大的價(jià)值。本文僅限于討論集成測(cè)試,主要是因?yàn)楣P者覺(jué)得這是一個(gè)非常重要卻常常被忽略的實(shí)踐。
基于Maven的一般流程
集成測(cè)試與單元測(cè)試最大的區(qū)別是它需要盡可能的測(cè)試整個(gè)功能及相關(guān)環(huán)境,對(duì)于測(cè)試Web應(yīng)用而言,通常有這么幾步:
啟動(dòng)Web容器
部署待測(cè)試Web應(yīng)用
以Web客戶端的角色運(yùn)行測(cè)試用例
停止Web容器
啟動(dòng)Web容器可以有很多方式,例如你可以通過(guò)Web容器提供的API采用編程的方式來(lái)啟動(dòng)容器,但在Maven的環(huán)境下,配置插件顯得更簡(jiǎn)單。如果你了解Maven的生命周期模型,就可能會(huì)想到,我們可以在pre-integration-test階段啟動(dòng)容器,部署待測(cè)試應(yīng)用,然后在integration-test階段運(yùn)行集成測(cè)試用例,最后在post-integrate-test階段停止容器。也就是說(shuō),對(duì)于步驟1,2和4我們只須進(jìn)行一些簡(jiǎn)單的配置,不必編寫(xiě)額外的代碼。第3步是以黑盒的形式模擬客戶端進(jìn)行測(cè)試,需要注意的是,這里通常要求你理解一些基本的HTTP協(xié)議知識(shí),例如服務(wù)端在什么情況下應(yīng)該返回HTTP代碼 200,什么時(shí)候應(yīng)該返回401錯(cuò)誤,以及所支持的Content-Type是什么等等。
至于測(cè)試用例該怎么寫(xiě),除了需要用到一些用來(lái)訪問(wèn)Web以及解析響應(yīng)詳細(xì)的基礎(chǔ)設(shè)施工具類(lèi)之外,其他內(nèi)容與單元測(cè)試大同小異,基本就是準(zhǔn)備測(cè)試數(shù)據(jù)、訪問(wèn)服務(wù)、驗(yàn)證返回值等等。
一個(gè)簡(jiǎn)單的例子
談了不少理論,現(xiàn)在該給個(gè)具體的例子了,譬如現(xiàn)在有個(gè)簡(jiǎn)單的Servlet,它接受參數(shù)a和b,做加法后返回二者之和,如果參數(shù)不完整,則返回HTTP 400錯(cuò)誤,表示客戶端的請(qǐng)求有問(wèn)題。
public class AddServletextends HttpServlet {@Overrideprotected void doGet( HttpServletRequest req, HttpServletResponse resp )throws ServletException,IOException{String a = req.getParameter( "a" );String b = req.getParameter( "b" );if ( a == null || b == null ){resp.setStatus( 400 );return;}int result = Integer.parseInt( a ) + Integer.parseInt( b );resp.setStatus( 200 );resp.getWriter().print( result );} }為了測(cè)試這段代碼,我們需要一個(gè)Web容器,這里暫且使用Jetty,因?yàn)槟壳皝?lái)說(shuō)它與Maven集成的相對(duì)最好。Jetty提供了一個(gè)Jetty Maven Plugin,借助該插件,我們可以隨時(shí)啟動(dòng)Jetty并部署Maven默認(rèn)目錄布局的Web項(xiàng)目,實(shí)現(xiàn)快速開(kāi)發(fā)和測(cè)試。這里我們需要的是在pre-integration-test階段啟動(dòng)Jetty,在post-integrate-test階段停止容器,對(duì)應(yīng)的POM配置如下:
<plugin><groupId>org.mortbay.jetty</groupId><artifactId>jetty-maven-plugin</artifactId><version>7.3.0.v20110203</version><configuration><stopPort>9966</stopPort><stopKey>stop-jetty-for-it</stopKey></configuration><executions><execution><id>start-jetty</id><phase>pre-integration-test</phase><goals><goal>run</goal></goals><configuration><daemon>true</daemon></configuration></execution><execution><id>stop-jetty</id><phase>post-integration-test</phase><goals><goal>stop</goal></goals></execution></executions></plugin>XML代碼中第一處configuration是插件的全局配置,stopPort和 stopKey是該插件用來(lái)停止Jetty需要用到的TCP端口及消息關(guān)鍵字。接著是兩個(gè)executation元素,第一個(gè)executation將 jetty-maven-plugin的run目標(biāo)綁定至Maven的pre-integration-test生命周期階段,表示啟動(dòng)容器,第二個(gè) executation將stop目標(biāo)綁定至post-integration-test生命周期階段,表示停止容器。需要注意的是,啟動(dòng)Jetty時(shí)我們需要配置deamon為true,讓Jetty在后臺(tái)運(yùn)行以免阻塞mvn命令。此外,jetty-maven-plugin的run目標(biāo)也會(huì)自動(dòng)部署當(dāng)前Web項(xiàng)目。
準(zhǔn)備好Web容器環(huán)境之后,我們接著看一下測(cè)試用例代碼:
public class AddServletIT {@Testpublic void addWithParametersAndSucceed()throws Exception{HttpClient httpclient = new DefaultHttpClient();HttpGet httpGet = new HttpGet( "http://localhost:8080/add?a=1&b=2" );HttpResponse response = httpclient.execute( httpGet );Assert.assertEquals( 200, response.getStatusLine().getStatusCode() );Assert.assertEquals( "3", EntityUtils.toString( response.getEntity() ) );}@Testpublic void addWithoutParameterAndFail()throws Exception{HttpClient httpclient = new DefaultHttpClient();HttpGet httpGet = new HttpGet( "http://localhost:8080/add" );HttpResponse response = httpclient.execute( httpGet );Assert.assertEquals( 400, response.getStatusLine().getStatusCode() );} }為了能夠訪問(wèn)應(yīng)用,這里用到了HttpClient,兩個(gè)測(cè)試方法都初始化一個(gè)HttpClient,然后創(chuàng)建HttpGet對(duì)象用來(lái)訪問(wèn)Web地址。第一個(gè)測(cè)試方法顧名思義用來(lái)測(cè)試成功的場(chǎng)景,它提供參數(shù) a=1和b=2,執(zhí)行請(qǐng)求后,驗(yàn)證返回結(jié)果成功(HTTP狀態(tài)碼200)并且內(nèi)容為正確的值3。第二個(gè)測(cè)試方法則用來(lái)測(cè)試失敗的場(chǎng)景,當(dāng)不提供參數(shù)的時(shí)候,服務(wù)器應(yīng)該返回一個(gè)HTTP 400錯(cuò)誤。該測(cè)試類(lèi)其實(shí)是相當(dāng)粗糙的,例如有硬編碼的服務(wù)器URL,這里的目的僅僅是通過(guò)盡可能簡(jiǎn)單的代碼來(lái)展現(xiàn)一個(gè)自動(dòng)化集成測(cè)試的實(shí)現(xiàn)過(guò)程。
上述代碼中,測(cè)試類(lèi)的名稱為AddServletIT,而不是一般的**Test,IT表示IntegrationTest,這么命名是為了和單元測(cè)試區(qū)分開(kāi)來(lái),這樣,鑒于Maven默認(rèn)的測(cè)試命名約定,Maven在test生命周期階段執(zhí)行單元測(cè)試時(shí),就不會(huì)涉及集成測(cè)試。現(xiàn)在,我們希望Maven在integration-test階段執(zhí)行所有以IT結(jié)尾命名的測(cè)試類(lèi),配置Maven Surefire Plugin如下:
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.7.2</version><executions><execution><id>run-integration-test</id><phase>integration-test</phase><goals><goal>test</goal></goals><configuration><includes><include>**/*IT.java</include></includes></configuration></execution></executions></plugin>通過(guò)命名規(guī)則和插件配置,我們優(yōu)雅地分離了單元測(cè)試和集成測(cè)試,而且我們知道在integration-test階段,Jetty容器已經(jīng)啟動(dòng)完成了。如果你在使用TestNG,那你還可以使用其測(cè)試組的特性來(lái)分離單元測(cè)試和集成測(cè)試,Maven Surefire Plugin對(duì)其也有著很好的支持。
一切就緒了,運(yùn)行?mvn clean install?以自動(dòng)運(yùn)行集成測(cè)試,我們可以看到如下的輸出片段:
[INFO] --- jetty-maven-plugin:7.3.0.v20110203:run (start-jetty) @ webapp-demo --- [INFO] Configuring Jetty for project: webapp-demo [INFO] webAppSourceDirectory /home/juven/git_juven/webapp-demo/src/main/webapp does not exist. Defaulting to /home/juven/git_juven/webapp-demo/src/main/webapp [INFO] Reload Mechanic: automatic [INFO] Classes = /home/juven/git_juven/webapp-demo/target/classes [INFO] Context path = / ... 2011-03-06 14:55:15.676:INFO::Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server [INFO] [INFO] --- maven-surefire-plugin:2.7.2:test (run-integration-test) @ webapp-demo --- [INFO] Surefire report directory: /home/juven/git_juven/webapp-demo/target/surefire-reports-------------------------------------------------------T E S T S ------------------------------------------------------- Running com.juvenxu.webapp.demo.AddServletIT Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.344 secResults :Tests run: 2, Failures: 0, Errors: 0, Skipped: 0[INFO] [INFO] --- jetty-maven-plugin:7.3.0.v20110203:stop (stop-jetty) @ webapp-demo ---可以看到j(luò)etty-maven-plugin:7.3.0.v20110203:run對(duì)應(yīng)了start-jetty,maven-surefire- plugin:2.7.2:test對(duì)應(yīng)了run-integration-test,jetty-maven- plugin:7.3.0.v20110203:stop對(duì)應(yīng)了stop-jetty,與我們的配置和期望完全一致。此外兩個(gè)測(cè)試也都成功了!
小結(jié)
相對(duì)于單元測(cè)試來(lái)說(shuō),集成測(cè)試更難編寫(xiě),因?yàn)樾枰獪?zhǔn)備更多的環(huán)境,本文只涉及了Web容器最簡(jiǎn)單的情形,實(shí)際的開(kāi)發(fā)情形中,你可能會(huì)遇到數(shù)據(jù)庫(kù),第三方Web服務(wù),更復(fù)雜的容器配置和數(shù)據(jù)格式等等,這都使得編寫(xiě)集成測(cè)試變得讓人畏懼。然而反過(guò)來(lái)考慮,無(wú)論如何你都需要測(cè)試,雖然這個(gè)自動(dòng)化過(guò)程的投入很大,但收益往往更加客觀,這不僅僅是手動(dòng)測(cè)試時(shí)間的節(jié)省,更重要的是,你無(wú)法保證手動(dòng)測(cè)試能被高頻率的反復(fù)執(zhí)行,也就無(wú)法保證問(wèn)題能被盡早暴露。
對(duì)于Web應(yīng)用來(lái)說(shuō),編寫(xiě)集成測(cè)試有助于你考慮和設(shè)計(jì)Web應(yīng)用對(duì)外暴露的接口,這種“開(kāi)發(fā)實(shí)現(xiàn)”/“測(cè)試審察”之間的角色轉(zhuǎn)換往往能造就更清晰的設(shè)計(jì),這也是編寫(xiě)測(cè)試最大的好處之一。
Maven用戶能夠得益于Maven的插件系統(tǒng),不僅能節(jié)省大量的編碼,還能得到穩(wěn)定的工具,Jetty Maven Plugin和Maven Surefire Plugin就是最好的例子。本文只涉及了Jetty,如果讀者的環(huán)境是Tomcat或者JBoss等其他容器,則需要查閱相關(guān)的文檔以得到具體的實(shí)現(xiàn)細(xì)節(jié),你可能對(duì)Tomcat Maven Plugin、JBoss Maven Plugin、或者Cargo Maven2 Plugin感興趣。
總結(jié)
以上是生活随笔為你收集整理的许晓斌_Maven实战(五)——自动化Web应用集成测试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 什么是反射(Reflection)?它能
- 下一篇: 第三方平台提供的L2十档行情API接口靠