JAVA代码覆盖率工具JaCoCo-原理简单分析
作為一個(gè)測(cè)試人員,保證產(chǎn)品的軟件質(zhì)量是其工作首要目標(biāo),為了這個(gè)目標(biāo),測(cè)試人員常常會(huì)通過很多手段或工具來加以保證,覆蓋率就是其中一環(huán)比較重要的環(huán)節(jié)。
我們通常會(huì)將測(cè)試覆蓋率分為兩個(gè)部分,即“需求覆蓋率”和“代碼覆蓋率”。
需求覆蓋:指的是測(cè)試人員對(duì)需求的了解程度,根據(jù)需求的可測(cè)試性來拆分成各個(gè)子需求點(diǎn),來編寫相應(yīng)的測(cè)試用例,最終建立一個(gè)需求和用例的映射關(guān)系,以用例的測(cè)試結(jié)果來驗(yàn)證需求的實(shí)現(xiàn),可以理解為黑盒覆蓋。
代碼覆蓋:為了更加全面的覆蓋,我們可能還需要理解被測(cè)程序的邏輯,需要考慮到每個(gè)函數(shù)的輸入與輸出,邏輯分支代碼的執(zhí)行情況,這個(gè)時(shí)候我們的測(cè)試執(zhí)行情況就以代碼覆蓋率來衡量,可以理解為白盒覆蓋。
以上兩者完全可以相輔相成,用代碼覆蓋結(jié)果反向的檢查需求覆蓋(用例)的測(cè)試是否充分完整。
如果做覆蓋率測(cè)試?我們可以借助一些網(wǎng)上流行的各種覆蓋率工具,本章主要介紹JaCoCo這個(gè)工具。
EMMA與JaCoco比較分析:
市場(chǎng)上java主要代碼覆蓋率工具:EMMA、JaCoCo
總結(jié)一下個(gè)人對(duì)JaCoCo優(yōu)勢(shì)的理解:
(1)JaCoCo支持分支覆蓋、引入了Agent模式。
(2)EMMA官網(wǎng)已經(jīng)不維護(hù)了,JaCoCo是其團(tuán)隊(duì)開發(fā)的,可以理解為一個(gè)升級(jí)版。
(3)JaCoCo社區(qū)比較活躍,官網(wǎng)也在不斷的維護(hù)更新。
JaCoCo是一個(gè)開源的覆蓋率工具(官網(wǎng)地址:http://www.eclemma.org/JaCoCo/),它針對(duì)的開發(fā)語言是java,其使用方法很靈活,可以嵌入到Ant、Maven中;可以作為Eclipse插件,可以使用其JavaAgent技術(shù)監(jiān)控Java程序等等。
##############################如果你不了解什么是java agent,下面接著有介紹,否則直接跳過####################################
JavaAgent 是JDK 1.5 以后引入的,也可以叫做Java代理。
JavaAgent 是運(yùn)行在 main方法之前的攔截器,它內(nèi)定的方法名叫 premain ,也就是說先執(zhí)行 premain 方法然后再執(zhí)行 main 方法。
那么如何實(shí)現(xiàn)一個(gè) JavaAgent 呢?很簡(jiǎn)單,只需要增加 premain 方法即可。
看下面的代碼和代碼中的注釋說明:
package com.shanhy.demo.agent;import java.lang.instrument.Instrumentation;/*** 我的Java代理** @author 單紅宇(365384722)* @myblog http://blog.csdn.net/catoop/* @create 2016年3月30日*/ public class MyAgent {/*** 該方法在main方法之前運(yùn)行,與main方法運(yùn)行在同一個(gè)JVM中* 并被同一個(gè)System ClassLoader裝載* 被統(tǒng)一的安全策略(security policy)和上下文(context)管理** @param agentOps* @param inst* @author SHANHY* @create 2016年3月30日*/public static void premain(String agentOps, Instrumentation inst) {System.out.println("=========premain方法執(zhí)行========");System.out.println(agentOps);}/*** 如果不存在 premain(String agentOps, Instrumentation inst) * 則會(huì)執(zhí)行 premain(String agentOps)** @param agentOps* @author SHANHY* @create 2016年3月30日*/public static void premain(String agentOps) {System.out.println("=========premain方法執(zhí)行2========");System.out.println(agentOps);} }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
寫完這個(gè)類后,我們還需要做一步配置工作。
在 src 目錄下添加 META-INF/MANIFEST.MF 文件,內(nèi)容按如下定義:
Manifest-Version: 1.0 Premain-Class: com.shanhy.demo.agent.MyAgent Can-Redefine-Classes: true- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
要特別注意,一共是四行,第四行是空行,還有就是冒號(hào)后面的一個(gè)空格,如下截圖:?
然后我們打包代碼為 myagent.jar
注意打包的時(shí)候選擇我們自己定義的 MANIFEST.MF?
接著我們?cè)趧?chuàng)建一個(gè)帶有main方法的主程序工程,截圖如下:?
然后將該主程序打包為 myapp.jar
如何執(zhí)行 myagent.jar ?我們通過 -javaagent 參數(shù)來指定我們的Java代理包,值得一說的是 -javaagent 這個(gè)參數(shù)的個(gè)數(shù)是不限的,如果指定了多個(gè),則會(huì)按指定的先后執(zhí)行,執(zhí)行完各個(gè) agent 后,才會(huì)執(zhí)行主程序的 main 方法。
命令如下:
java -javaagent:G:\myagent.jar=Hello1 -javaagent:G:\myagent.jar=Hello2 -javaagent:G:\myagent.jar=Hello3 -jar myapp.jar- 1
- 1
輸出結(jié)果:
G:\>java -javaagent:G:\myagent.jar=Hello1 -javaagent:G:\myagent.jar=Hello2 -javaagent:G:\myagent.jar=Hello3 -jar myapp.jar =========premain方法執(zhí)行======== Hello1 =========premain方法執(zhí)行======== Hello2 =========premain方法執(zhí)行======== Hello3 =========main方法執(zhí)行========- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
特別提醒:如果你把 -javaagent 放在 -jar 后面,則不會(huì)生效。也就是說,放在主程序后面的 agent 是無效的。
比如執(zhí)行:
java -javaagent:G:\myagent.jar=Hello1 -javaagent:G:\myagent.jar=Hello2 -jar myapp.jar -javaagent:G:\myagent.jar=Hello3- 1
- 1
只會(huì)有前個(gè)生效,第三個(gè)是無效的。?
輸出結(jié)果:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
命令中的Hello1為我們傳遞給 premain 方法的字符串參數(shù)。
至此,我們會(huì)使用 javaagent 了,但是單單看這樣運(yùn)行的效果,好像沒有什么實(shí)際意義嘛。
我們可以用 javaagent 做什么呢?下篇文章我們來介紹如何在項(xiàng)目中應(yīng)用 javaagent。
最后說一下,還有一種,在main方法執(zhí)行后再執(zhí)行代理的方法,因?yàn)椴怀S?#xff0c;而且主程序需要配置 Agent-Class,所以不常用,如果需要自行了解下 agentmain(String agentArgs, Instrumentation inst) 方法。
#################################################### java agent介紹結(jié)束 #####################################################
很多第三方的工具提供了對(duì)JaCoCo的集成,如sonar、Jenkins等。
JaCoCo包含了多種尺度的覆蓋率計(jì)數(shù)器,包含指令級(jí)覆蓋(Instructions,C0coverage),分支(Branches,C1coverage)、圈復(fù)雜度(CyclomaticComplexity)、行覆蓋(Lines)、方法覆蓋(non-abstract methods)、類覆蓋(classes)。
行覆蓋率:度量被測(cè)程序的每行代碼是否被執(zhí)行,判斷標(biāo)準(zhǔn)行中是否至少有一個(gè)指令被執(zhí)行。
類覆蓋率:度量計(jì)算class類文件是否被執(zhí)行。
分支覆蓋率:度量if和switch語句的分支覆蓋情況,計(jì)算一個(gè)方法里面的
總分支數(shù),確定執(zhí)行和不執(zhí)行的 分支數(shù)量。
方法覆蓋率:度量被測(cè)程序的方法執(zhí)行情況,是否執(zhí)行取決于方法中是否有至少一個(gè)指令被執(zhí)行。
指令覆蓋:計(jì)數(shù)單元是單個(gè)java二進(jìn)制代碼指令,指令覆蓋率提供了代碼是否被執(zhí)行的信息,度量完全 獨(dú)立源碼格式。
圈復(fù)雜度:在(線性)組合中,計(jì)算在一個(gè)方法里面所有可能路徑的最小數(shù)目,缺失的復(fù)雜度同樣表示測(cè) 試案例沒有完全覆蓋到這個(gè)模塊。
jacoco原理:
1. 注入方式介紹
這個(gè)圖包含了幾種不同的收集覆蓋率信息的方法,每種方法的實(shí)現(xiàn)方法都不一樣,帶顏色的部分是JaCoCo比較有特色的地方。
上面各個(gè)名次含義(帶顏色的為JaCoCo支持):
上表JaCoCo支持的部分,再詳細(xì)的解釋下:
(1)JaCoCo在Byte Code時(shí)使用的ASM技術(shù)修改字節(jié)碼方法,可以修改Jar文件、class文件字節(jié)碼文件。
(2)JaCoCo同時(shí)支持on-the-fly和offline的兩種插樁模式。
On-the-fly插樁:JVM中通過-javaagent參數(shù)指定特定的jar文件啟動(dòng)Instrumentation的代理程序,代理程序在通過Class Loader裝載一個(gè)class前判斷是否轉(zhuǎn)換修改class文件,將統(tǒng)計(jì)代碼插入class,測(cè)試覆蓋率分析可以在JVM執(zhí)行測(cè)試代碼的過程中完成。
Offline模式:在測(cè)試前先對(duì)文件進(jìn)行插樁,然后生成插過樁的class或jar包,測(cè)試插過樁 的class和jar包后,會(huì)生成動(dòng)態(tài)覆蓋信息到文件,最后統(tǒng)一對(duì)覆蓋信息進(jìn)行處理,并生成報(bào)告。
On-the-fly和offline比較:On-the-fly模式更方便簡(jiǎn)單進(jìn)行代碼覆蓋分析,無需提前進(jìn)行字節(jié)碼插樁,無需考慮classpath 的設(shè)置。
存在如下情況不適合on-the-fly,需要采用offline提前對(duì)字節(jié)碼插樁:
(1)運(yùn)行環(huán)境不支持java agent。
(2)部署環(huán)境不允許設(shè)置JVM參數(shù)。
(3)字節(jié)碼需要被轉(zhuǎn)換成其他的虛擬機(jī)如Android Dalvik VM。
(4)動(dòng)態(tài)修改字節(jié)碼過程中和其他agent沖突。
(5)無法自定義用戶加載類。
2. JaCoCo執(zhí)行最小的java版本
最小需要Java1.5
3. 字節(jié)碼處理方式
JaCoCo通過注入來修改和生成java字節(jié)碼,使用的是ASM庫(kù)。
4. java方法控制流分析
JaCoCo是如何在字節(jié)碼注入的?
先舉個(gè)實(shí)例,有個(gè)java方法:
編譯后轉(zhuǎn)換成字節(jié)碼后,內(nèi)容如下:
我們知道JaCoCo是字節(jié)碼注入方式,它是通過一個(gè)Probe探針的方式來注入的,具體如下:
探針是字節(jié)指令集插入到j(luò)ava方法中,程序執(zhí)行后可以被記錄,它不會(huì)改變?cè)写a的行為。
我們看看探針前后插入比較:
顏色的部分就是探針注入的地方。
JaCoCo是根據(jù)控制流Type來采用不同的探針插入策略的。
一個(gè)用java字節(jié)碼定義的java方法的控制流圖可能有以下的type,每一個(gè)type連接一個(gè)源指令與目標(biāo)指令,type不同探針的注入策略也會(huì)不同,如下是type定義:
總結(jié)
以上是生活随笔為你收集整理的JAVA代码覆盖率工具JaCoCo-原理简单分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何使用 Canvas 制作出炫酷的网页
- 下一篇: 执行 java -jar xxx.ja