加速Java應用開發速度3——單元/集成測試+CI——僅做我的記錄學習(轉載)

你們可能對以下情景比較熟悉:javascript

  • 若是開發過SSH的web項目,啓動服務器可能會比較慢,有的項目甚至須要1分多鐘,甚至更多,這個啓動時間的等待通常就浪費了;
  • 在開發項目時,有些功能比較複雜,當時以爲思路特清晰,可是過了一段時間後,本身也忘了,完善功能時頻繁出現bug,下降開發速度;
  • 在維護項目時,不知道本身修改的對仍是不對,是否存在隱患;維護速度降下來了;
  • 若是開發一個不少人都使用的接口,典型的如用戶系統,要保證好比升級時向下兼容;
  • 在團隊間協做時,有時候只定義好接口,對方尚未給實現,如何進行同步開發?

如上問題,估計只要是個開發人員,均可能遇到過;若是此時有了單元/集成測試,那咱們能很好的解決這些問題。(注:加下來若是沒有特殊狀況,不刻意強調 單元測試/集成測試,即提到測試是指的是單元/集成測試)php

 

我從如下幾個方面介紹測試:html

一、爲何須要測試?java

二、如何進行測試?node

三、測試有哪些好處?python

四、一切都須要測試嗎?mysql

 

一、爲何須要測試?

測試的目的是什麼?個人理解是:git

  • 縮短髮現問題到解決問題的速度;
  • 給程序一個修改後能驗證是否正確的保證;(迴歸測試)
  • 若是是開源軟件,咱們能夠經過單元測試瞭解其是怎麼使用的;好比我以前經過cglib的單元測試學習過cglib的使用;

因此若是你遇到如上問題,就須要寫測試。寫測試多是爲了本身(一、2);也多是爲了幫助別人(3)。github

 

二、如何進行測試?

不少朋友不知道如何進行測試,其實測試很簡單,別把它想複雜了,按照本身的想法測試每一個功能點是否正確便可。web

2.一、測試流程

單元測試流程

 

集成測試流程

 

集成測試流程 

 

 

能夠看出,單元測試與集成測試惟一不一樣點是一個調用依賴系統而一個不調用;由於單元測試是最小粒度的測試,如在Java中是測試一個類,不會測試依賴系統;而集成測試是會測試依賴系統的。

 

測試的步驟:

  1. 準備環境
  2. 調用被測系統
  3. 驗證
  4. 清理環境

環境:也叫作夾具(fixture)或者固件,表示調用被測系統時須要準備/清理的數據等等;

被測系統:在Java中就是要測試的類,如UserService;

依賴系統:測試被測系統時,其依賴的部分,如UserDao;

測試用例:包含測試方法的類,裏邊有不少測試方法來測試被測系統。

 

接下來仔細看看各部分都作了哪些工做。

 

2.二、環境

 環境,也叫作夾具(fixture),表示調用被測系統時須要準備/清理的數據等等;保證測試時環境是乾淨的,如不被以前的數據庫數據影響;保證每次測試都是在乾淨/新鮮的環境中執行的。所謂乾淨的環境表示如當前測試不被以前測試插入/刪除/修改的數據形成影響。在junit中可使用:

  • @Before(setUp) 安裝夾具或準備環境:在測試用例的每一個測試方法以前執行;好比建立新鮮的被測系統,單元測試時安裝Mock的依賴系統;
  • @After(tearDown)卸載夾具或清理環境:在測試用例的每一個測試方法以後執行;好比數據庫測試時回滾事務,刪除數據;關閉文件;
  • @BeforeClass:在整個測試用例以前執行;
  • @AfterClass:在整個測試用例以後執行;

使用如上方法,而不是直接在測試方法中安裝/卸載;是由於無論有沒有異常,@After/@AfterClass都會執行,這樣防止出現異常可能形成環境是不新鮮的問題。

 

若是你們使用spring test來測試數據庫相關的系統,能夠考慮使用@TransactionConfiguration來支持默認事務回滾,這樣不會對現有系統形成影響。具體可參考《【第十三章】 測試 之 13.1 概述 13.2 單元測試 ——跟我學spring3》和《【第十三章】 測試 之 13.3 集成測試 ——跟我學spring3

 

測試時必定要保證環境是乾淨/新鮮的,才能保證每次測試的結果是同樣的。

 

2.三、被測系統與依賴系統

被測系統:在Java中就是被測試的Java類。

依賴系統:就是被測試Java類依賴的其餘類。

 

若是是單元測試,通常狀況下,會對依賴系統進行模擬(Mock),即給它一個假的實現;典型的如測試服務層時注入一個Mock的DAO層,這樣的好處:

  • 加快測試速度;由於不會調用真實的被測系統,因此速度特別快;
  • 測試尚未完成的功能;尤爲在多團隊協做時,能夠只在定義好接口的狀況下開發系統;

 

若是是集成測試時,直接注入真實的依賴系統便可,好處:

  • 完成聯調;
  • 發現本身的問題;
  • 還可能發現本身使用上問題及使用的API的問題;

單元測試雖然好,可是是隔離測試,即不會調用被測系統來完成測試,由於不是真實的聯調,因此極可能會潛在有一些問題,所以仍是須要集成測試。(因此不是很刻意分單元或集成測試,且有些系統可能只有集成測試)

 

可是集成測試速度是比較慢的,通常提交給CI執行,不影響當前開發進度。

 

2.四、驗證

驗證的目的:是保證明際結果和咱們預期的結果是否一致,說白了就是是不是咱們想的那樣。

 

通常使用斷言來驗證,如:

Assert.assertEquals(expectedResult, actualResult); //驗證預期結果和實際結果是否相等

 

驗證主要有兩種:

  • 結果驗證
  • 行爲驗證

結果驗證:即驗證被測系統返回的結果是否正確,如:

Java代碼   收藏代碼
  1. @Test  
  2. public void testCount() {  
  3.     String ql = "select count(o) from User o";  
  4.     long expectedCount = repositoryHelper.count(ql) + 1;  
  5.   
  6.     User user = createUser();  
  7.     repositoryHelper.getEntityManager().persist(user);  
  8.   
  9.     long acutalCount = repositoryHelper.count(ql);  
  10.     Assert.assertEquals(expectedCount, acutalCount);  
  11.   
  12. }  

驗證返回的數據總數 = 插入以前的總數 + 1; 即結果驗證。此處咱們使用了一種叫作相對(delta)測試;即不關心數據庫裏到底多少條,只關心實際的和預期的差。

 

行爲驗證:即驗證被測系統是否調用了依賴系統的某個API ,這個只有當咱們使用Mock時測試時比較簡單,如當用戶註冊時:

一、加積分

二、發系統消息

三、……

此時咱們並不能經過結果驗證是否調用了這些方法;那麼咱們可使用Mock技術來完成驗證是否調用了這些API,好比使用jmock測試框架就支持行爲驗證。集成測試是很難進行行爲驗證的,若是測試須要預留間諜接口。

 

三、測試有哪些好處?

咱們寫代碼的目的是正確的完成某個功能,如何保證正確呢?測試!因此在不使用如單元測試技術時,咱們也是須要測試,可是這個測試是咱們人工驗證的。缺點很明顯:

  • 不是自動的,每次須要對比預期結果與實際結果,尤爲數據量/邏輯複雜時更痛苦;
  • 不是迴歸的,上次測試完成後,下次還得重複本身一遍;

爲了解決這個問題,咱們使用如單元測試技術來解決這個問題:

  • 測試自動化;即驗證預期結果與實際結果交給計算機吧;
  • 測試迴歸性,能夠重複執行測試,驗證修改後邏輯是否仍是正確的;

即測試的好處,從如上已經提煉出來了:

  • 縮短髮現問題到解決問題的時間;
  • 重複使用測試,保證修改後的代碼仍是正確的;
  • 若是作開源項目,能夠提供給使用人員參考如何使用;
  • 由於單元測試都很是快,因此提高了開發速度;

四、一切都須要測試嗎?

確定不是,一切都是相對的;哪些不須要測試呢:

  • 你很是熟悉的功能;

  • 一些簡單的CRUD;

  • 你認爲不須要測試的;好比你頗有把握的東西,就沒有必要浪費時間測試了;

哪些須要測試呢:

  • 複雜的業務邏輯/系統核心功能,最典型的如訂單系統:必定要有足夠的單元測試保證,這是一個電商系統的核心;還有如用戶系統、積分系統等等;
  • 框架級別/工具級別/通用級別的代碼須要測試,即提供給第三方使用的代碼,由於這些代碼可能被不少系統依賴,應該保證其正確性;並且還要保證之後版本升級的向下兼容;
  • 你認爲須要測試的,好比你沒有把握的東西,仍是寫點測試來縮短如開發web項目的重啓系統的時間吧;

 測試不是不耗時間的,沒意義的測試就是浪費時間,最典型是一些書上的對一個增刪改查進行測試,實際項目沒有任何意義。因此你應該只對本身很難駕馭的以爲有必要的代碼進行測試。不要成爲一個測試狂,什麼都測試。 

 

一些測試能夠參考個人《es——JavaEE快速開發腳手架》中的代碼。經過測試我獲得了許多好處。 

 

到此咱們介紹完成了測試,可是若是咱們使用瞭如集成測試時,測試執行起來可能比較慢,跑一遍測試可能須要5分鐘,那怎麼辦呢?

  • 天天下班前跑一遍集成測試,而後修復,下班走人;

  • CI:持續集成,交給持續集成服務器,自動地測試完成後把測試報告以郵件的形式發到開發人員郵箱;

 

------------------------------------分割線----------------------------------

 

 

接下來介紹一下CI吧。

一、爲何須要CI

二、CI如何工做的

三、travis-ci介紹

 

一、爲何須要CI

正如前邊說的,咱們單獨測試可能會遇到以下問題:

  • 若是寫了一個測試,就要把全部測試跑一遍看看整個系統是不是正確的,那麼每次等待時間是很是漫長的;
  • 若是團隊中的其餘成員改了功能並提交了,如何快速獲得該次提交對當前系統代碼是正確仍是失敗的反饋;

那怎麼辦呢?自動化地持續集成(CI)!CI的核心就是幹這件事情的。自動化持續地集成測試。

 

使用CI後,若是使用Maven,能夠新建多個profile:

  • 本地測試時忽略一些比較慢的測試;
  • CI服務器上執行全部測試;

 

二、CI如何工做的

一個典型的持續集成流程:

 

  1. 按期檢測版本服務器上是否有代碼更新;
  2. 若是發現代碼更新,從版本服務器下載最新的代碼;
  3. 自動構建並自動化的測試;
  4. 無論錯誤/失敗,生成報告給開發人員;
  5. 有些CI服務器還能產生可執行的軟件,自動化地部署到測試機器,交給測試人員測試。

如圖所示:

 
 

 

持續集成服務器其實就是一個定時器,自動幫你下載最新代碼、編譯、測試、集成及產生報告發給開發人員。

 

常見的CI服務器有:

  • Apache Continuum
  • Hudson
  • CruiseControl
  • Jenkins CI
  • TeamCity 
  • Travis CI

 

我09年時使用過TeamCity社區版,足夠知足常見需求;目前我使用github託管項目,使用Travis CI進行分佈式的持續集成,免費,目前看來仍是不錯的。

 

三、travis-ci介紹

我如今開發的ES-JavaEE項目開發腳手架就是使用travis ci進行持續集成;具體參考《Getting started》進行與Github集成,其支持的語言:

支持的數據庫:

  • MySQL
  • PostgreSQL
  • MongoDB
  • CouchDB
  • Redis
  • Riak
  • RabbitMQ
  • Memcached
  • Cassandra
  • Neo4J
  • ElasticSearch
  • Kestrel
  • SQLite3

更多請參考其官網的介紹。

 

 

若是是Java開發人員,支持的JDK包括:OpenJDK 和 OracleJDK。 若是使用的是OpenJDK,Maven中使用ascii2native插件時,須要以下配置: 

Java代碼   收藏代碼
  1. <plugin>  
  2.     <groupId>org.codehaus.mojo</groupId>  
  3.     <artifactId>native2ascii-maven-plugin</artifactId>  
  4.     <version>1.0-alpha-1</version>  
  5.     <executions>  
  6.         <execution>  
  7.             <phase>generate-resources</phase>  
  8.             <goals>  
  9.                 <goal>native2ascii</goal>  
  10.             </goals>  
  11.             <configuration>  
  12.                 <encoding>UTF-8</encoding>  
  13.                 <src>src/main/messages</src>  
  14.                 <dest>target/${project.artifactId}/WEB-INF/classes</dest>  
  15.                 <includes>messages.properties</includes>  
  16.             </configuration>  
  17.         </execution>  
  18.     </executions>  
  19.     <!-- native2ascii 使用的tools.jar -->  
  20.     <dependencies>  
  21.         <dependency>  
  22.             <groupId>com.sun</groupId>  
  23.             <artifactId>tools</artifactId>  
  24.             <version>1.7.0</version>  
  25.             <scope>system</scope>  
  26.             <systemPath>${java.home}/../lib/tools.jar</systemPath>  
  27.         </dependency>  
  28.     </dependencies>  
  29. </plugin>  

若是使用mysql,端口只能是3306。

若是想開端口測試,這是不容許的。

 

 

以下是我項目中的一個配置.travis.yml,放到項目的根下便可:

-----------------------------------

language: java           語言

 

env:                           環境

  - DB=mysql              使用mysql

 

jdk:

  - openjdk                jdk使用openjdk

 

mysql: 

  database: es         數據庫名爲es

  username: root     用戶名爲root

  password :            密碼爲空

  encoding: utf8      編碼爲utf8

 

install:                     安裝時執行的腳本

  - mvn install -Dmaven.test.skip=true     mvn安裝並跳過測試

 

before_script:        script以前執行的測試

  - cd web              

  - mvn db:create  建立數據庫的mvn命令(此處使用了 maven-db-plugin 插件)

  - mvn db:schema  建立腳本的mvn命令

  - mvn db:data        安裝數據的mvn命令

  - cd ..

 

script:                      測試時執行的腳步

  - cd common 

  - mvn test              測試common子模塊

  - cd ..

  - cd web

  - mvn test -Pit       測試web子模塊,並指定使用it profile測試(即集成測試的配置,具體參考pom.xml中的profile/it)

 

notifications:          觸發

  email:                  測試完成後測試報告發到哪

    - zhangkaitao0503@gmail.com  

-----------------------------------

 

 

持續集成不能修復代碼的錯誤,而是和單元測試同樣,縮短髮現問題帶解決問題的時間,這樣能夠提升開發效率,下降項目風險,提升項目的穩定性。並且尤爲是團隊協做時,能夠發現其餘人的代碼是否對本身的代碼產生影響。 

 

 

到此咱們利用單元測試+CI能夠加速開發人員的開發速度。利用好單元測試和CI,不要純粹爲了單元測試和CI而去作這些事情。

 

本文沒有介紹TDD,TDD並不會那麼美好,我認爲咱們能夠借鑑TDD的一些思想,但決不能迷信TDD,有時候,尤爲如開發企業應用,先寫功能再寫測試可能效率更高,並且大部分時候是不須要TDD的。並且我也沒能在實際項目中獲取太多TDD的好處,可是我得到了測試的好處。

 

本文也沒有介紹測試覆蓋率,我認爲不要一味的追求覆蓋率,有時候有的覆蓋率沒有任何意義。因此不要讓爲了覆蓋率而覆蓋率拖慢了項目開發進度。

 

 

正如stackoverflow上的一篇帖子《How deep are your unit tests?》上Kent Beck的回答:

寫道
老闆爲個人代碼付報酬,而不是測試,因此,我對此的價值觀是——測試越少越好,少到你對你的代碼質量達到了某種自信。

能夠前往coolshell「單元測試要作多細?」去獲得一些經驗。

       

 

推薦閱讀:

es——JavaEE快速開發腳手架中的一些測試用例

【第十三章】 測試 之 13.1 概述 13.2 單元測試 ——跟我學spring3

【第十三章】 測試 之 13.3 集成測試 ——跟我學spring3

stamen的單元測試系列

TDD並非看上去的那麼美

「單元測試要作多細?」

持續集成(第二版)

《xUnit測試模式》

《持續集成:軟件質量改進和風險下降之道》《持續交付--發佈可靠軟件的系統方法》

相關文章
相關標籤/搜索