引言
經常有人問這樣的問題:“我們在做單元測試,那測試覆蓋率要到多少才行?”。答案其實很簡答,“作爲指標的測試覆蓋率都是沒有用處的。”
Martin Fowler(重構那本書的作者)曾經寫過一篇博客來討論這個問題,他指出:把測試覆蓋作爲質量目標沒有任何意義,而我們應該把它作爲一種發現未被測試覆蓋的代碼的手段。
代碼覆蓋率的意義
- 分析未覆蓋部分的代碼,從而反推在前期測試設計是否充分,沒有覆蓋到的代碼是否是測試設計的盲點,爲什麼沒有考慮到?需求/設計不夠清晰,測試設計的理解有誤,工程方法應用後的造成的策略性放棄等等,之後進行補充測試用例設計。
- 檢測出程序中的廢代碼,可以逆向反推在代碼設計中思維混亂點,提醒設計/開發人員理清代碼邏輯關係,提升代碼質量。
- 代碼覆蓋率高不能說明代碼質量高,但是反過來看,代碼覆蓋率低,代碼質量不會高到哪裏去,可以作爲測試自我審視的重要工具之一。
代碼覆蓋率工具
目前Java常用覆蓋率工具Jacoco、Emma和Cobertura
覆蓋率工具工作流程
1. 對Java字節碼進行插樁,On-The-Fly和Offine兩種方式。
2. 執行測試用例,收集程序執行軌跡信息,將其dump到內存。
3. 數據處理器結合程序執行軌跡信息和代碼結構信息分析生成代碼覆蓋率報告。
4. 將代碼覆蓋率報告圖形化展示出來,如html、xml等文件格式。
插樁原理
主流代碼覆蓋率工具都採用字節碼插樁模式,通過鉤子的方式來記錄代碼執行軌跡信息。其中字節碼插樁又分爲兩種模式On-The-Fly和Offine。On-The-Fly模式優點在於無需修改源代碼,可以在系統不停機的情況下,實時收集代碼覆蓋率信息。Offine模式優點在於系統啓動不需要額外開啓代理,但是只能在系統停機的情況下才能獲取代碼覆蓋率。 基於以上特性,同時由於公司使用JDK8,我們採用Jacoco來獲取集成測試代碼覆蓋率,單元測試使用Cobertura。
On-The-Fly插樁 Java Agent
- JVM中通過-javaagent參數指定特定的jar文件啓動Instrumentation的代理程序
- 代理程序在每裝載一個class文件前判斷是否已經轉換修改了該文件,如果沒有則需要將探針插入class文件中。
- 代碼覆蓋率就可以在JVM執行代碼的時候實時獲取。
- 典型代表:Jacoco
On-The-Fly插樁 Class Loader
- 自定義classloader實現自己的類裝載策略,在類加載之前將探針插入class文件中
- 典型代表:Emma
Offine插樁
- 在測試之前先對文件進行插樁,生成插過樁的class文件或者jar包,執行插過樁的class文件或者jar包之後,會生成覆蓋率信息到文件,最後統一對覆蓋率信息進行處理,並生成報告。
- Offline插樁又分爲兩種:
- Replace:修改字節碼生成新的class文件
- Inject:在原有字節碼文件上進行修改
- 典型代表:Cobertura
On-The-Fly和Offine比較
- On-The-Fly模式更加方便的獲取代碼覆蓋率,無需提前進行字節碼插樁,可以實時獲取代碼覆蓋率信息
- Offline模式適用於以下場景:
- 運行環境不支持java agent
- 部署環境不允許設置JVM參數
- 字節碼需要被轉換成其他虛擬機字節碼,如Android Dalvik VM
- 動態修改字節碼過程中和其他agent衝突
- 無法自定義用戶加載類
實踐應用
單元測試覆蓋率
目前有贊開發人員會寫單元測試用例,爲了能夠引入持續集成,我們選取了Sonar+Cobertura來獲取單元測試覆蓋率。 我們將代碼覆蓋率綁定到代碼編譯階段,這樣每次代碼編譯就能夠執行單元測試同時獲取代碼單元測試覆蓋率
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.7</version>
<configuration>
<formats>
<format>xml</format>
</formats>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>cobertura</goal>
</goals>
</execution>
</executions>
</plugin>
123456789101112131415161718
生成代碼覆蓋率文件以後,通過Jenkins SonarQube Scanner或者執行mvn sonar:sonar
將該文件上傳至Sonar 服務器,就可以解析該文件,生成圖形化的界面
集成測試覆蓋率
測試人員執行集成測試測試用例時(包括手工執行和自動化執行),我們需要代碼覆蓋率來發現測試用例設計的遺漏,及時補充用例來覆蓋未被覆蓋到的代碼。
被測系統,在服務啓動時,都會通過javaagent的方式做On-The-Fly插樁
- 被測服務器啓動之後,測試人員手工執行測試用例,Jacoco Agent會實時將代碼覆蓋率信息傳輸給Jacoco Prase Server,該服務器保存了被測代碼源文件以及編譯後的目標文件,服務器會結合源文件、目標文件以及代碼覆蓋率信息生成圖表化的覆蓋率文件。
- 自動化執行測試用例完成之後,獲取代碼覆蓋率信息,通過Jenkins Jacoco插件解析,獲取圖表化的覆蓋率文件。獲取代碼覆蓋率報告之後,結合git獲取的本次代碼變動信息,得到測試用例覆蓋的變動文件的測試覆蓋率統計信息。來分析是否有由於測試用例設計遺漏導致的代碼沒有覆蓋或者是開發的無效代碼導致該代碼無法被覆蓋,如果測試用例設計有所遺漏,可以對照的增加相應的用例;如果是無效代碼可以刪除。
自動化集成流程
1. 業務開發完成之後,開發人員做單元測試,單元測試完成之後,保證單元測試全部通過同時單元測試代碼覆蓋率達到一定程度(這個需要開發和測試約定,理論上越高越好),開發提測。
2. 測試人員根據測試用例進行測試(包括手工測試和自動化測試),結合git獲取本次變動代碼的覆蓋率信息。行覆蓋率需達到100%,分支達到50%以上,這個需要具體場景具體分析。
3. 測試通過之後,代碼合併至主幹,進行自動化迴歸。
4. 迴歸測試通過之後,代碼可以上線。
基於這套流程,我們可以將單元測試代碼覆蓋率和集成測試代碼覆蓋率整合到持續集成流程中,如果代碼覆蓋率達不到我們設置的某個值時,可以終止流程繼續下去獲取需要人工確認之後,繼續流程。
總結
本文主要介紹了Java代碼覆蓋率統計原理以及結合有贊測試的工程實踐介紹了代碼覆蓋率該如何應用的實際測試中。不管是白盒測試還是黑盒測試,代碼覆蓋率統計都是必不可少的一環,它可以直接反映本次測試的遺漏點(不是100%反映)。結合到自動發佈場景也是一個較好地衡量指標。
最後再重申一下本文開篇的觀點:
- 代碼覆蓋率統計是用來發現沒有被測試覆蓋的代碼
- 代碼覆蓋率統計不能完全用來衡量代碼質量