
保證所有項目的目錄結構在任何服務器上都是一樣的,每個目錄起什么作用都很清楚明了。
3、統一軟件構建階段
http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
Maven2把軟件開發的過程劃分成了幾個經典階段,比如你先要生成一些java代碼,再把這些代碼復制到特定位置,然后編譯代碼,復制需要放到classpath下的資源,再進行單元測試,單元測試都通過了才能進行打包,發布。
測試框架:JUnit&Mockito
JUnit
JUnit是一個Java語言的單元測試框架。
2013年見過一個舊項目,測試代碼還是以main作為入口,為什么要使用JUnit?
JUnit 的優點是整個測試過程無人值守,開發無須在線參與和判斷最終結果是否正確,可以很容易地一次性運行多個測試,使得開發更加關注測試邏輯的編寫,而不是增加構建維護時間。
團隊示例代碼:
// 功能代碼 package com.chinacache.portal.service; public class ReportService { public boolean validateParams() { } public String sendReport(Long id) { } public String sendReport(Long id, Date time) { } } // 單元測試代碼 package com.chinacache.portal.service; // 必須與功能代碼使用相同 package public class ReportServiceUnitTest { // 測試類名以 UnitTest (單元測試) 或 InteTest (集成測試) 結尾 // 測試方法名以 test 開頭,然后接對應的功能方法名稱 @Test public void testValidateParams() { } // 如果功能方法存在重載,則再接上參數類型 @Test public void testSendReportLong() { } // 如果一個功能方法對應多個測試方法,不同測試方法可使用簡潔而又有含義的單詞結尾,例如 success、fail 等 @Test public void testSendReportLongDateSuccess() { } // 這樣通過測試方法名即可知道:測的是哪個功能方法,哪種情況 @Test public void testSendReportLongDateFail() { } } |
Mockito
Mockito是一個針對Java的mocking框架。使用它可以寫出干凈漂亮的測試用例和簡單的API。它與EasyMock和jMock很相似,通過在執行后校驗什么已經被調用,消除了對期望行為(expectations)的需要,改變其他mocking庫以“記錄-回放”(這會導致代碼丑陋)的測試流程,使得自身的語法更像自然語言。
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); |
官方對比文章:http://code.google.com/p/mockito/wiki/MockitoVSEasyMock
反饋平臺:Jenkins&Sonar
持續集成平臺:Jenkins
Jenkins 的前身是 Hudson 是一個可擴展的持續集成引擎,主要用于:
持續、自動地構建測試軟件項目
監控一些定時執行的任務
Jenkins將作為自動化單元測試持續集成的平臺,實現自動化構建。
圖-2-3-Jenkins平臺
代碼質量管理平臺:Sonar
Sonar (SonarQube)是一個開源平臺,用于管理源代碼的質量。Sonar 不只是一個質量數據報告工具,更是代碼質量管理平臺。支持的語言包括:Java、PHP、C#、C、Cobol、PL/SQL、Flex 等。
主要特點:
代碼覆蓋:通過單元測試,將會顯示哪行代碼被選中
改善編碼規則
搜尋編碼規則:按照名字,插件,激活級別和類別進行查詢
項目搜尋:按照項目的名字進行查詢
對比數據:比較同一張表中的任何測量的趨勢
Sonar將作為自動化單元測試反饋報告統一展現平臺,包括:
單元測試覆蓋率、成功率、代碼注釋、代碼復雜度等度量數據的展現。
圖-2-4 Sonar平臺
3 原則
自動化測試金字塔,也稱為自動化分層測試,Unit是整個金字塔的基石,最重要特點是運行速度非常快;第二個重要特點是UT應覆蓋代碼庫的大部分,能夠確定一旦UT通過后,應用程序就能正常工作。
Unit:70%,大部分自動化實現,用于驗證一個單獨函數或獨立功能模塊的代碼;
Service:20%,涉及兩個或兩個以上,甚至更多模塊之間交互的集成測試;
UI:10%,覆蓋三個或以上的功能模塊,真實用戶場景和數據的驗收測試;
這里僅僅列舉了每個層次的百分比,實際要根據團隊的方向來做調整。
自動化單元測試原則
提交代碼、運行測試的重點是什么?快速捕獲那些因修改向系統中引入的最常見錯誤,并通知開發人員,以便他們能快速修復他們。提交階段提供反饋的價值在于,對它的投入可以讓系統高效且更快地工作。
隔離UI操作
UI應當作為更高層次的測試Level,需要花費大量時間準備數據,業務邏輯復雜,過早進入UI階段,容易分散開發的單元測試精力。
隔離數據庫以及文件讀寫網絡開銷等操作
自動化測試中如果需要將結果寫入數據庫,然后再驗證改結果是否被正確寫入,這種驗證方法簡單、容易理解,但是它不是一個高效的方法。這個應當從集成測試的Level去解決。
首先:與數據庫的交互,是漫長的,甚至有可能要投入維護數據庫的時間,那將成為快速測試的一個障礙,開發人員不能得到及時有效的反饋。假設,我需要花費一個小時,才能驗證完畢與數據庫交互的結果,這種等待是多么漫長呀。
其次,數據管理需要成本,從數據的篩選(線上數據可能是T級)到測試環境的M級別,如何把篩選合適的大小,這都使得管理成本增加(當然在集成測試中可以使用DBUnit來解決部分問題)。
最后,如果一定要有讀寫操作才能完成的測試,也要反思代碼的可測試性做的如何?是否需要重構。
單元測試決不要依賴于數據庫以及文件系統、網絡開銷等一切外部依賴。
使用Mock替身與Spring容器隔離
如果在單元測試中,還需要啟動Spring容器進行依賴注入、加載依賴的WebService等,這個過程是相當消耗時間的。
可以使用模擬工具集:Mockito、EasyMock、JMock等來解決,研發團隊主要是基于Mockito的實踐。與需要組裝所有的依賴和狀態相比,使用模擬技術的測試運行起來通常是非常快,這樣子開發人員在提交代碼之后,可以在持續集成平臺快速得到反饋。
設計簡單的測試
明確定義方法:
成功:public void testSendReportLongDateSuccess()
失敗:public void testSendReportLongDateFail(),可以包括異常
和單一的斷言,避免在一個方法內使用多個復雜斷言,這會造成代碼結構的復雜,使得測試的復雜性提高。
定義測試套件的運行時間
使用Mock構建的單元測試,每個方法的構建時間應該是毫秒級別,整個類是秒級別,理想的是整體構建時間控制在5分鐘以內,如果超過怎么辦呢?
首先,拆分成多個套件,在多臺機器上并行執行這些套件;
其次,重構那些運行時間比較長且不經常失敗的測試類;
更多參考推薦閱讀:《Unit Testing Guidelines》
http://geosoft.no/development/unittesting.html
4 流程
圖-4-1-典型工作流程
開發人員遵循每日構建原則,提交功能代碼、測試代碼(以UnitTest結尾的測試類)到Svn;
Jenkins平臺,根據配置原則(假設配置定時器每6分鐘檢查Svn有代碼更新則構建)進行:代碼更新、代碼編譯、UnitTest、持續反饋的流水線工作;
構建結果發送到Sonar,并且把失敗的構建以郵件方式通知影響代碼的開發人員;
開發人員、測試人員需要在Sonar平臺進行review;
5 實踐
Jenkins配置重點
構建觸發器:推薦使用PollSCM
Poll SCM:定時檢查源碼變更(根據SCM軟件的版本號),如果有更新就執行checkout。
Build periodically:周期進行項目構建(它不care源碼是否發生變化)。
配置時間: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,參數配置,運行以UnitTest結尾的測試類;
sonar:sonar,來觸發靜態代碼分析。
需要安裝Emma Plugin(https://wiki.jenkins-ci.org/display/JENKINS/Emma+Plugin)
構建后操作
增加Aggregate downstream test results,勾選自動整合所有的downstream測試;
增加Editable Email Notification,在“高級”選項增加觸發器“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的單元測試覆蓋率展現方式,可以查看官網。
構建管理工具(Maven)
項目統一使用Maven進行構建管理,在pom.xml中進行依賴jar包配置
持續集成服務器上同時需要安裝Maven,setting.xml除了配置倉庫之外,還需要配置sonar,包括sonar服務器地址、數據庫連接方式:
<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配置重點
所有單元測試繼承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 測試環境。繼承該類后,Mockito 的相關注解 (@Mock, @InjectMocks, ...) 就能生效 */ public class MockitoTestContext { public MockitoTestContext() { MockitoAnnotations.initMocks(this); } } BillingBusinessManager 源碼: package com.chinacache.portal.service.billing; //引入包忽略... /** * 計費業務相關的業務方法 */ @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; /** * 根據id查詢業務信息 * @return 如果參數為空或者查詢不到數據,返回空列表 * O 中的中、英文業務名來自 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")); //簡潔的語法如下所示 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的使用,可以參考官網:http://code.google.com/p/mockito/
6 總結
如何加強開發過程中的自測環節,一直都是個頭痛的問題,開發的代碼質量究竟如何?模塊之間的質量究竟如何?回歸測試的效率如何?重構之后,如何快速驗證模塊的有效性?
這些在沒有做自動化單元測試之前,都是難以考究的問題。唯有通過數據去衡量,橫向對比多個版本的構建分析結果,才能夠發現整個項目質量的趨勢,是提升了,還是下降了,這樣開發、測試人員才能夠有信心做出恰當的判斷。
當然,單元測試也不是銀彈,即便項目的覆蓋率達到100%,也不能表明產品質量沒有任何問題,不會產生任何缺陷。重點在于確保單元測試環節的實施,可以提前釋放壓力、風險、暴露問題等多個方面,改變以往沒有單元測試,所有問題都集中到最后爆發的弊端。
最后,用一張圖來做個對比:
圖-6-1-使用前后對比
增加單元測試之后:
開發效率有望提升5-20%;重構、回歸測試效率提升10%,降低出錯的幾率,總體代
碼質量提升;
在開發過程中暴露更多問題,將風險和壓力提前釋放,持續構建促使開發重視代碼質量;
UnitTest質量對于團隊來說,是可視化了,交付的是有質量的產品,而不是數量;