
保證所有項(xiàng)目的目錄結(jié)構(gòu)在任何服務(wù)器上都是一樣的,每個(gè)目錄起什么作用都很清楚明了。
3、統(tǒng)一軟件構(gòu)建階段
http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
Maven2把軟件開發(fā)的過(guò)程劃分成了幾個(gè)經(jīng)典階段,比如你先要生成一些java代碼,再把這些代碼復(fù)制到特定位置,然后編譯代碼,復(fù)制需要放到classpath下的資源,再進(jìn)行單元測(cè)試,單元測(cè)試都通過(guò)了才能進(jìn)行打包,發(fā)布。
測(cè)試框架:JUnit&Mockito
JUnit
JUnit是一個(gè)Java語(yǔ)言的單元測(cè)試框架。
2013年見過(guò)一個(gè)舊項(xiàng)目,測(cè)試代碼還是以main作為入口,為什么要使用JUnit?
JUnit 的優(yōu)點(diǎn)是整個(gè)測(cè)試過(guò)程無(wú)人值守,開發(fā)無(wú)須在線參與和判斷最終結(jié)果是否正確,可以很容易地一次性運(yùn)行多個(gè)測(cè)試,使得開發(fā)更加關(guān)注測(cè)試邏輯的編寫,而不是增加構(gòu)建維護(hù)時(shí)間。
團(tuán)隊(duì)示例代碼:
// 功能代碼 package com.chinacache.portal.service; public class ReportService { public boolean validateParams() { } public String sendReport(Long id) { } public String sendReport(Long id, Date time) { } } // 單元測(cè)試代碼 package com.chinacache.portal.service; // 必須與功能代碼使用相同 package public class ReportServiceUnitTest { // 測(cè)試類名以 UnitTest (單元測(cè)試) 或 InteTest (集成測(cè)試) 結(jié)尾 // 測(cè)試方法名以 test 開頭,然后接對(duì)應(yīng)的功能方法名稱 @Test public void testValidateParams() { } // 如果功能方法存在重載,則再接上參數(shù)類型 @Test public void testSendReportLong() { } // 如果一個(gè)功能方法對(duì)應(yīng)多個(gè)測(cè)試方法,不同測(cè)試方法可使用簡(jiǎn)潔而又有含義的單詞結(jié)尾,例如 success、fail 等 @Test public void testSendReportLongDateSuccess() { } // 這樣通過(guò)測(cè)試方法名即可知道:測(cè)的是哪個(gè)功能方法,哪種情況 @Test public void testSendReportLongDateFail() { } } |
Mockito
Mockito是一個(gè)針對(duì)Java的mocking框架。使用它可以寫出干凈漂亮的測(cè)試用例和簡(jiǎn)單的API。它與EasyMock和jMock很相似,通過(guò)在執(zhí)行后校驗(yàn)什么已經(jīng)被調(diào)用,消除了對(duì)期望行為(expectations)的需要,改變其他mocking庫(kù)以“記錄-回放”(這會(huì)導(dǎo)致代碼丑陋)的測(cè)試流程,使得自身的語(yǔ)法更像自然語(yǔ)言。
Mockito示例:
List mock = mock(List.class); when(mock.get(0)).thenReturn("one"); when(mock.get(1)).thenReturn("two"); someCodeThatInteractsWithMock(); verify(mock).clear(); EasyMock示例: List mock = createNiceMock(List.class); expect(mock.get(0)).andStubReturn("one"); expect(mock.get(1)).andStubReturn("two"); mock.clear(); replay(mock); someCodeThatInteractsWithMock(); verify(mock); |
官方對(duì)比文章:http://code.google.com/p/mockito/wiki/MockitoVSEasyMock
反饋平臺(tái):Jenkins&Sonar
持續(xù)集成平臺(tái):Jenkins
Jenkins 的前身是 Hudson 是一個(gè)可擴(kuò)展的持續(xù)集成引擎,主要用于:
持續(xù)、自動(dòng)地構(gòu)建測(cè)試軟件項(xiàng)目
監(jiān)控一些定時(shí)執(zhí)行的任務(wù)
Jenkins將作為自動(dòng)化單元測(cè)試持續(xù)集成的平臺(tái),實(shí)現(xiàn)自動(dòng)化構(gòu)建。
圖-2-3-Jenkins平臺(tái)
代碼質(zhì)量管理平臺(tái):Sonar
Sonar (SonarQube)是一個(gè)開源平臺(tái),用于管理源代碼的質(zhì)量。Sonar 不只是一個(gè)質(zhì)量數(shù)據(jù)報(bào)告工具,更是代碼質(zhì)量管理平臺(tái)。支持的語(yǔ)言包括:Java、PHP、C#、C、Cobol、PL/SQL、Flex 等。
主要特點(diǎn):
代碼覆蓋:通過(guò)單元測(cè)試,將會(huì)顯示哪行代碼被選中
改善編碼規(guī)則
搜尋編碼規(guī)則:按照名字,插件,激活級(jí)別和類別進(jìn)行查詢
項(xiàng)目搜尋:按照項(xiàng)目的名字進(jìn)行查詢
對(duì)比數(shù)據(jù):比較同一張表中的任何測(cè)量的趨勢(shì)
Sonar將作為自動(dòng)化單元測(cè)試反饋報(bào)告統(tǒng)一展現(xiàn)平臺(tái),包括:
單元測(cè)試覆蓋率、成功率、代碼注釋、代碼復(fù)雜度等度量數(shù)據(jù)的展現(xiàn)。
圖-2-4 Sonar平臺(tái)
3 原則
自動(dòng)化測(cè)試金字塔,也稱為自動(dòng)化分層測(cè)試,Unit是整個(gè)金字塔的基石,最重要特點(diǎn)是運(yùn)行速度非常快;第二個(gè)重要特點(diǎn)是UT應(yīng)覆蓋代碼庫(kù)的大部分,能夠確定一旦UT通過(guò)后,應(yīng)用程序就能正常工作。
Unit:70%,大部分自動(dòng)化實(shí)現(xiàn),用于驗(yàn)證一個(gè)單獨(dú)函數(shù)或獨(dú)立功能模塊的代碼;
Service:20%,涉及兩個(gè)或兩個(gè)以上,甚至更多模塊之間交互的集成測(cè)試;
UI:10%,覆蓋三個(gè)或以上的功能模塊,真實(shí)用戶場(chǎng)景和數(shù)據(jù)的驗(yàn)收測(cè)試;
這里僅僅列舉了每個(gè)層次的百分比,實(shí)際要根據(jù)團(tuán)隊(duì)的方向來(lái)做調(diào)整。
自動(dòng)化單元測(cè)試原則
提交代碼、運(yùn)行測(cè)試的重點(diǎn)是什么?快速捕獲那些因修改向系統(tǒng)中引入的最常見錯(cuò)誤,并通知開發(fā)人員,以便他們能快速修復(fù)他們。提交階段提供反饋的價(jià)值在于,對(duì)它的投入可以讓系統(tǒng)高效且更快地工作。
隔離UI操作
UI應(yīng)當(dāng)作為更高層次的測(cè)試Level,需要花費(fèi)大量時(shí)間準(zhǔn)備數(shù)據(jù),業(yè)務(wù)邏輯復(fù)雜,過(guò)早進(jìn)入U(xiǎn)I階段,容易分散開發(fā)的單元測(cè)試精力。
隔離數(shù)據(jù)庫(kù)以及文件讀寫網(wǎng)絡(luò)開銷等操作
自動(dòng)化測(cè)試中如果需要將結(jié)果寫入數(shù)據(jù)庫(kù),然后再驗(yàn)證改結(jié)果是否被正確寫入,這種驗(yàn)證方法簡(jiǎn)單、容易理解,但是它不是一個(gè)高效的方法。這個(gè)應(yīng)當(dāng)從集成測(cè)試的Level去解決。
首先:與數(shù)據(jù)庫(kù)的交互,是漫長(zhǎng)的,甚至有可能要投入維護(hù)數(shù)據(jù)庫(kù)的時(shí)間,那將成為快速測(cè)試的一個(gè)障礙,開發(fā)人員不能得到及時(shí)有效的反饋。假設(shè),我需要花費(fèi)一個(gè)小時(shí),才能驗(yàn)證完畢與數(shù)據(jù)庫(kù)交互的結(jié)果,這種等待是多么漫長(zhǎng)呀。
其次,數(shù)據(jù)管理需要成本,從數(shù)據(jù)的篩選(線上數(shù)據(jù)可能是T級(jí))到測(cè)試環(huán)境的M級(jí)別,如何把篩選合適的大小,這都使得管理成本增加(當(dāng)然在集成測(cè)試中可以使用DBUnit來(lái)解決部分問(wèn)題)。
最后,如果一定要有讀寫操作才能完成的測(cè)試,也要反思代碼的可測(cè)試性做的如何?是否需要重構(gòu)。
單元測(cè)試決不要依賴于數(shù)據(jù)庫(kù)以及文件系統(tǒng)、網(wǎng)絡(luò)開銷等一切外部依賴。
使用Mock替身與Spring容器隔離
如果在單元測(cè)試中,還需要啟動(dòng)Spring容器進(jìn)行依賴注入、加載依賴的WebService等,這個(gè)過(guò)程是相當(dāng)消耗時(shí)間的。
可以使用模擬工具集:Mockito、EasyMock、JMock等來(lái)解決,研發(fā)團(tuán)隊(duì)主要是基于Mockito的實(shí)踐。與需要組裝所有的依賴和狀態(tài)相比,使用模擬技術(shù)的測(cè)試運(yùn)行起來(lái)通常是非常快,這樣子開發(fā)人員在提交代碼之后,可以在持續(xù)集成平臺(tái)快速得到反饋。
設(shè)計(jì)簡(jiǎn)單的測(cè)試
明確定義方法:
成功:public void testSendReportLongDateSuccess()
失敗:public void testSendReportLongDateFail(),可以包括異常
和單一的斷言,避免在一個(gè)方法內(nèi)使用多個(gè)復(fù)雜斷言,這會(huì)造成代碼結(jié)構(gòu)的復(fù)雜,使得測(cè)試的復(fù)雜性提高。
定義測(cè)試套件的運(yùn)行時(shí)間
使用Mock構(gòu)建的單元測(cè)試,每個(gè)方法的構(gòu)建時(shí)間應(yīng)該是毫秒級(jí)別,整個(gè)類是秒級(jí)別,理想的是整體構(gòu)建時(shí)間控制在5分鐘以內(nèi),如果超過(guò)怎么辦呢?
首先,拆分成多個(gè)套件,在多臺(tái)機(jī)器上并行執(zhí)行這些套件;
其次,重構(gòu)那些運(yùn)行時(shí)間比較長(zhǎng)且不經(jīng)常失敗的測(cè)試類;
更多參考推薦閱讀:《Unit Testing Guidelines》
http://geosoft.no/development/unittesting.html
4 流程
圖-4-1-典型工作流程
開發(fā)人員遵循每日構(gòu)建原則,提交功能代碼、測(cè)試代碼(以UnitTest結(jié)尾的測(cè)試類)到Svn;
Jenkins平臺(tái),根據(jù)配置原則(假設(shè)配置定時(shí)器每6分鐘檢查Svn有代碼更新則構(gòu)建)進(jìn)行:代碼更新、代碼編譯、UnitTest、持續(xù)反饋的流水線工作;
構(gòu)建結(jié)果發(fā)送到Sonar,并且把失敗的構(gòu)建以郵件方式通知影響代碼的開發(fā)人員;
開發(fā)人員、測(cè)試人員需要在Sonar平臺(tái)進(jìn)行review;
5 實(shí)踐
Jenkins配置重點(diǎn)
構(gòu)建觸發(fā)器:推薦使用PollSCM
Poll SCM:定時(shí)檢查源碼變更(根據(jù)SCM軟件的版本號(hào)),如果有更新就執(zhí)行checkout。
Build periodically:周期進(jìn)行項(xiàng)目構(gòu)建(它不care源碼是否發(fā)生變化)。
配置時(shí)間:H/6 * * * *
Build配置
Goals and options:emma:emma -Dtest=*UnitTest soanr:sonar
注明:
emma:emma,Add the "emma:emma" goal to your build to generate Emma reports;
-Dtest=*UnitTest,參數(shù)配置,運(yùn)行以UnitTest結(jié)尾的測(cè)試類;
sonar:sonar,來(lái)觸發(fā)靜態(tài)代碼分析。
需要安裝Emma Plugin(https://wiki.jenkins-ci.org/display/JENKINS/Emma+Plugin)
構(gòu)建后操作
增加Aggregate downstream test results,勾選自動(dòng)整合所有的downstream測(cè)試;
增加Editable Email Notification,在“高級(jí)”選項(xiàng)增加觸發(fā)器“Unstable”,
勾選“Send To Committers”,Check this checkbox to send the email to anyone who checked in code for the last build。
注明:Editable Email Notification插件是 https://wiki.jenkins-ci.org/display/JENKINS/Email-ext+plugin
另外一些Jenkins的單元測(cè)試覆蓋率展現(xiàn)方式,可以查看官網(wǎng)。
構(gòu)建管理工具(Maven)
項(xiàng)目統(tǒng)一使用Maven進(jìn)行構(gòu)建管理,在pom.xml中進(jìn)行依賴jar包配置
持續(xù)集成服務(wù)器上同時(shí)需要安裝Maven,setting.xml除了配置倉(cāng)庫(kù)之外,還需要配置sonar,包括sonar服務(wù)器地址、數(shù)據(jù)庫(kù)連接方式:
<profile> <id>sonar</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <!-- EXAMPLE FOR MYSQL --> <sonar.jdbc.url> jdbc:mysql://127.0.0.1:3306/sonar?useUnicode=true&characterEncoding=utf8 </sonar.jdbc.url> <sonar.jdbc.driverClassName>com.mysql.jdbc.Driver</sonar.jdbc.driverClassName> <sonar.jdbc.username>sonar</sonar.jdbc.username> <sonar.jdbc.password>sonar</sonar.jdbc.password> <!-- SERVER ON A REMOTE HOST --> <sonar.host.url>http:/127.0.0.1:9000</sonar.host.url> </properties> </profile> |
Mockito配置重點(diǎn)
所有單元測(cè)試?yán)^承MockitoTestContext父類
MockitoTestContext 父類:
package com.chinacache.portal; import java.util.Locale; import org.junit.BeforeClass; import org.mockito.MockitoAnnotations; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.chinacache.portal.web.util.SessionUtil; import com.opensymphony.xwork2.util.LocalizedTextUtil; /** * Mockito 測(cè)試環(huán)境。繼承該類后,Mockito 的相關(guān)注解 (@Mock, @InjectMocks, ...) 就能生效 */ public class MockitoTestContext { public MockitoTestContext() { MockitoAnnotations.initMocks(this); } } BillingBusinessManager 源碼: package com.chinacache.portal.service.billing; //引入包忽略... /** * 計(jì)費(fèi)業(yè)務(wù)相關(guān)的業(yè)務(wù)方法 */ @Transactional public class BillingBusinessManager { private static final Log log = LogFactory.getLog(BillingBusinessManager.class); @Autowired private UserDAO userDAO; @Autowired private BillingBusinessDAO billingBusinessDAO; @Autowired private BillingBusinessSubscriptionDAO billingBusinessSubscriptionDAO; @Autowired private BillingBusinessSubscriptionDetailDAO billingBusinessSubscriptionDetailDAO; @Autowired private BillingRegionSubscriptionDAO billingRegionSubscriptionDAO; @Autowired private BillingRegionDAO billingRegionDAO; @Autowired private ContractTimeManager contractTimeManager; /** * 根據(jù)id查詢業(yè)務(wù)信息 * @return 如果參數(shù)為空或者查詢不到數(shù)據(jù),返回空列表 * O 中的中、英文業(yè)務(wù)名來(lái)自 BILLING_BUSINESS 表 */ public List getBusinessesByIds(List businessIds) { return billingBusinessDAO.getBusinessbyIds(businessIds); } } BillingBusinessManagerUnitTest類: //引入包忽略... public class BillingBusinessManagerUnitTest extends MockitoTestContext { @InjectMocks private BillingBusinessManager sv; @Mock private BillingBusinessDAO billingBusinessDAO; @Test public void testGetBusinessesByIds() { List<BusinessVO> expected = ListUtil.toList(new BusinessVO(1l, "a", "b")); //簡(jiǎn)潔的語(yǔ)法如下所示 when(billingBusinessDAO.getBusinessbyIds(anyListOf(Long.class))).thenReturn(expected); List<Long> businessIds = ListUtil.toList(TestConstants.BUSINESS_ID_HTTP_WEB_CACHE); List<BusinessVO> actual = sv.getBusinessesByIds(businessIds); Assert.assertEquals(expected, actual); } } |
更多Mockito的使用,可以參考官網(wǎng):http://code.google.com/p/mockito/
6 總結(jié)
如何加強(qiáng)開發(fā)過(guò)程中的自測(cè)環(huán)節(jié),一直都是個(gè)頭痛的問(wèn)題,開發(fā)的代碼質(zhì)量究竟如何?模塊之間的質(zhì)量究竟如何?回歸測(cè)試的效率如何?重構(gòu)之后,如何快速驗(yàn)證模塊的有效性?
這些在沒(méi)有做自動(dòng)化單元測(cè)試之前,都是難以考究的問(wèn)題。唯有通過(guò)數(shù)據(jù)去衡量,橫向?qū)Ρ榷鄠€(gè)版本的構(gòu)建分析結(jié)果,才能夠發(fā)現(xiàn)整個(gè)項(xiàng)目質(zhì)量的趨勢(shì),是提升了,還是下降了,這樣開發(fā)、測(cè)試人員才能夠有信心做出恰當(dāng)?shù)呐袛唷?/div>
當(dāng)然,單元測(cè)試也不是銀彈,即便項(xiàng)目的覆蓋率達(dá)到100%,也不能表明產(chǎn)品質(zhì)量沒(méi)有任何問(wèn)題,不會(huì)產(chǎn)生任何缺陷。重點(diǎn)在于確保單元測(cè)試環(huán)節(jié)的實(shí)施,可以提前釋放壓力、風(fēng)險(xiǎn)、暴露問(wèn)題等多個(gè)方面,改變以往沒(méi)有單元測(cè)試,所有問(wèn)題都集中到最后爆發(fā)的弊端。
最后,用一張圖來(lái)做個(gè)對(duì)比:
圖-6-1-使用前后對(duì)比
增加單元測(cè)試之后:
開發(fā)效率有望提升5-20%;重構(gòu)、回歸測(cè)試效率提升10%,降低出錯(cuò)的幾率,總體代
碼質(zhì)量提升;
在開發(fā)過(guò)程中暴露更多問(wèn)題,將風(fēng)險(xiǎn)和壓力提前釋放,持續(xù)構(gòu)建促使開發(fā)重視代碼質(zhì)量;
UnitTest質(zhì)量對(duì)于團(tuán)隊(duì)來(lái)說(shuō),是可視化了,交付的是有質(zhì)量的產(chǎn)品,而不是數(shù)量;