Java
Jacoco
Ant
Maven
近期因工作需要本讥,需對代碼覆蓋率進行統(tǒng)計主籍,所以這篇就當做對這段時間學習的總結肾扰。
總得來說網(wǎng)上找到的資料都不系統(tǒng)突硝,不適合新手理解和參考榨惰,下面我就以我一個小白的親身體驗,將我
踩到的那些坑和遇到的那些疑惑記錄下來
(作為一名初學者挖胃,文章中可能會有錯誤或者理解偏差的地方雷袋,歡迎各位批評指正)
- 針對 Jacoco
+
Jenkins+
SonarQube&
SonarQube Scanner 分為四個部分寫的,建議閱讀的順序為:- Jacoco Code Coverage ?
- Jenkins + Jacoco 持續(xù)集成代碼覆蓋率
- SonarQube & SonarQube Scanner
- Jenkins + SonarQube & SonarQube Scanner
代碼覆蓋率工具調(diào)研信息如下:
- 市場上主要代碼覆蓋率工具:
- Emma
- Cobertura
- Jacoco
- Clover(商用)
具體見下表:
工具 | Jacoco | Emma | Cobertura |
---|---|---|---|
原理 | 使用 ASM 修改字節(jié)碼 | 修改 jar 文件密似,class 文件字節(jié)碼文件 | 基于 jcoverage,基于 asm 框架對 class 文件插樁 |
覆蓋粒度 | 行焙矛,類,方法残腌,指令薄扁,分支 | 行,類废累,方法邓梅,基本塊,指令邑滨,無分支覆蓋 | 項目日缨,包,類掖看,方法的語句覆蓋/分支覆蓋 |
插樁 | on the fly匣距、offline | on the fly面哥、offline | offline,把統(tǒng)計代碼插入編譯好的class文件中 |
生成結果 | 在 Tomcat 的 catalina.sh 配置 javaangent 參數(shù)毅待,指出需要收集覆蓋率的文件尚卫,shutdown 時才收集,只能使用 kill 命令關閉 Tomcat尸红,不要使用 kill -9 | html吱涉、xml、txt外里,二進制格式報表 | html怎爵,xml |
缺點 | 需要源代碼 | 1、需要 debug 版本盅蝗,并打來 build.xml 中的 debug 編譯項鳖链; 2、需要源代碼墩莫,且必須與插樁的代碼完全一致 | 1芙委、不能捕獲測試用例中未考慮的異常; 2狂秦、關閉服務器才能輸出覆蓋率信息(已有修改源代碼的解決方案灌侣,定時輸出結果;輸出結果之前設置了 hook故痊,會與某些服務器的 hook 沖突顶瞳,web 測試中需要將 cobertura.ser 文件來回 copy |
性能 | 快 | 小巧 | 插入的字節(jié)碼信息更多 |
執(zhí)行方式 | maven玖姑,ant愕秫,命令行 | 命令行 | maven,ant |
Jenkins 集成 | 生成 html 報告焰络,直接與 hudson 集成戴甩,展示報告,無趨勢圖 | 無法與 hudson 集成 | 有集成的插件闪彼,美觀的報告甜孤,有趨勢圖 |
報告實時性 | 默認關閉,可以動態(tài)從 jvm dump 出數(shù)據(jù) | 可以不關閉服務器 | 默認是在關閉服務器時才寫結果 |
維護狀態(tài) | 持續(xù)更新中 | 停止維護 | 停止維護 |
Tip:Jacoco 也是
Emma 團隊開發(fā)的
JaCoCo Java Code Coverage Library
Jacoco 是一個開源的覆蓋率工具畏腕。Jacoco 可以嵌入到 Ant 缴川、Maven 中,并提供了 EclEmma Eclipse 插件,也可以使用 Java Agent 技術監(jiān)控 Java 程序描馅。很多第三方的工具提供了對 Jacoco 的集成把夸,如:Sonar、Jenkins铭污、IDEA.
Java Counters
Jacoco 包含了多種尺度的覆蓋率計數(shù)器,包含指令級(Instructions,C0 coverage)恋日,分支(Branches,C1 coverage)膀篮、圈復雜度(Cyclomatic Complexity)、行(Lines)岂膳、方法(Non-abstract Methods)誓竿、類(Classes)。
? Instructions:Jacoco 計算的最小單位就是字節(jié)碼指令谈截。指令覆蓋率表明了在所有的指令中筷屡,哪些被執(zhí)行過以及哪些沒有被執(zhí)行。這項指數(shù)完全獨立于源碼格式并且在任何情況下有效傻盟,不需要類文件的調(diào)試信息速蕊。
? Branches:Jacoco 對所有的 if 和 switch 指令計算了分支覆蓋率。這項指標會統(tǒng)計所有的分支數(shù)量娘赴,并同時支出哪些分支被執(zhí)行规哲,哪些分支沒有被執(zhí)行。這項指標也在任何情況都有效诽表。異常處理不考慮在分支范圍內(nèi)唉锌。
在有調(diào)試信息的情況下,分支點可以被映射到源碼中的每一行竿奏,并且被高亮表示袄简。
紅色鉆石:無覆蓋,沒有分支被執(zhí)行泛啸。
黃色鉆石:部分覆蓋绿语,部分分支被執(zhí)行。
綠色鉆石:全覆蓋候址,所有分支被執(zhí)行吕粹。
? Cyclomatic Complexity:Jacoco 為每個非抽象方法計算圈復雜度,并也會計算每個類岗仑、包匹耕、組的復雜度。根據(jù) McCabe 1996 的定義荠雕,圈復雜度可以理解為覆蓋所有的可能情況最少使用的測試用例數(shù)稳其。這項參數(shù)也在任何情況下有效。
? Lines:該項指數(shù)在有調(diào)試信息的情況下計算炸卑。
因為每一行代碼可能會產(chǎn)生若干條字節(jié)碼指令既鞠,所以我們用三種不同狀態(tài)表示行覆蓋率
紅色背景:無覆蓋,該行的所有指令均無執(zhí)行盖文。
黃色背景:部分覆蓋嘱蛋,該行部分指令被執(zhí)行。
綠色背景:全覆蓋,該行所有指令被執(zhí)行浑槽。
? Methods:每一個非抽象方法都至少有一條指令蒋失。若一個方法至少被執(zhí)行了一條指令,就認為它被執(zhí)行過桐玻。因為 Jacoco 直接對字節(jié)碼進行操作篙挽,所以有些方法沒有在源碼顯示(比如某些構造方法和由編譯器自動生成的方法)也會被計入在內(nèi)。
? Classes:每個類中只要有一個方法被執(zhí)行镊靴,這個類就被認定為被執(zhí)行铣卡。同 5 一樣,有些沒有在源碼聲明的方法被執(zhí)行偏竟,也認定該類被執(zhí)行煮落。
Jacoco 原理
參考資料:
好了,廢話不多說踊谋,咱們直奔主題蝉仇,大家只要按照操作步驟執(zhí)行就可以
Jacoco 收集集成測試代碼覆蓋率
什么是集成測試?
-
準備工作
- 下載 jacoco.zip 包
第一步:將下載下來的 zip 包與 Tomcat 服務放在一臺機器上
-
第二步:在
[yourTomcatPath]/bin/catalina.sh
添加 Jacoco 插件殖蚕,指令如下??JAVA_OPTS="-javaagent:[yourPath/]jacocoagent.jar=includes=com.companyName.*,output=tcpserver,port=8044,address=100.44.44.144,append=true -Xverify:none"
Tip:添加插件之前轿衔,須將的 Tomcat 服務停掉之后再添加,添加完之后睦疫,再啟動 Tomcat 服務
參數(shù)說明: 1. yourPath 是放 jacocoagent.jar 文件的目錄路徑害驹;那么 `jacocoagent.jar` 這個 `jar` 包的路徑就是在準備工作里下載下來的 `zip` 包,解壓之后的 `lib` 目錄下蛤育,如:'/jacoco-0.7.9/lib/jacocoagent.jar' 2. includes 是指要收集哪些類(注意不要僅寫包名宛官,最后要寫.*),不寫的話默認是*瓦糕,會收集應用服務上所有的類底洗,包括服務器和其他中間件的類,一般要過濾(當然如果你愿意寫*也完全沒有問題刻坊,如:`includes=com.*` or `includes=*`)枷恕,如果要指定多個的話党晋,即這樣寫 `includes=com.package.1:com.package.2`(切記指定多個時中間用英文 `:` 隔開)谭胚; 3. output 有 4 個值,分別是 file未玻、tcpserver灾而、tcpclient、mbean扳剿,默認是 file旁趟。使用 file 的方式只有在停掉應用服務的時候才能產(chǎn)生覆蓋率文件,而使用 tcpserver 的方式可以在不停止應用服務的情況下下載覆蓋率文件庇绽,后面會介紹如何使用 dump 方法來得到覆蓋率文件锡搜。 4. address 是 IP 地址橙困,IP 就是 Tomcat 服務器的機器的 IP,至于是寫 `服務器本機的 IP` 還是寫 `127.0.0.1` 要看情況 1) 如果是在 Tomcat 服務器上執(zhí)行 `ant dump` 的話耕餐,就直接寫 `address=127.0.0.1` 2) 如果執(zhí)行 `ant dump` 不是在 Tomcat 服務器上執(zhí)行的凡傅,就得寫服務器本機的IP(切記) 5. port 是端口(端口比較隨便,找個能用的端口就行肠缔,直接我為什么將端口寫成 `8044`夏跷,我的想法是 `BUG 死死` 與 `8044` 挺配的,所以就用它作為端口號了) (`address` 和 `port` 是使用 tcpserver 方式需要的 2 個參數(shù)明未,也是執(zhí)行 ant dump 方法必須要用到的槽华。) 6. append 表示覆蓋率數(shù)據(jù)的追加方式,默認為 true趟妥∶ㄌ客戶端在執(zhí)行 dump 操作時,如果該 exec 覆蓋率文件已存在披摄,那么該輪的覆蓋率數(shù)據(jù)會直接在文本末尾進行追加懂鸵,因此會導致覆蓋率數(shù)據(jù)文件越來越大。如果改為 false行疏,則客戶端執(zhí)行 dump 操作時會直接清空原覆蓋率文件的內(nèi)容匆光,保證該覆蓋率文件只有該輪的覆蓋率數(shù)據(jù)。 7. `-Xverify:none`:這個參數(shù)是防止啟動主程序異常才加的(非強制酿联,可以不加)
Tip
:更多參數(shù)說明终息,請點擊 這里
啟動 Tomcat 服務之后,ps 一下贞让,如果在 Tomcat 服務中有 jacocoagent 這個服務的話 |
---|
那么恭喜你周崭,你成功了!T拧续镇! |
- 第三步:獲取報告
ant dump
(也是就上文中提到的,特別提醒:這里使用ant
命令销部,和你的代碼工程使用什么編譯工具編譯的沒有一點關系摸航,不要混淆)
build.xml
文件內(nèi)容如下??
<?xml version="1.0" encoding="UTF-8"?>
<project name="Jacoco" xmlns:jacoco="antlib:org.jacoco.ant" default="jacoco">
<property name="jacocoantPath" value="[yourPath/]jacocoant.jar"/>
<property name="integrationJacocoexecPath" value="./jacoco-integration.exec"/>
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="${jacocoantPath}" />
</taskdef>
<target name="dump">
<jacoco:dump address="100.44.44.144" port="8044" reset="false" destfile="${integrationJacocoexecPath}" append="true"/>
</target>
</project>
`.exec`:二進制文件,Jacoco 就是根據(jù)這個文件生成最終的報告
`destfile`:是指生成的覆蓋率文件路徑
Tip:
build.xml 只需修改三個點舅桩,就可以直接拿去用
第一個修改點:補全 `jacocoant.jar` 路徑酱虎。(那么 `jacocoant.jar` 在哪?對于這個問題擂涛,或許會有疑問读串,當然,如果細心的小伙伴就會很輕易的發(fā)現(xiàn) `jacocoant.jar` 的位置,其實也就在準備工作中所下載的 `zip` 包里面恢暖,與 `jacocoagent.jar` 在同級目錄 `lib` 文件夾下)
第二個修改點:修改 IP 地址(IP 須與 `catalina.sh` 中添加的一致)
第三個修改點:修改端口號(與IP一樣排监,端口號須與 `catalina.sh` 中添加的一致)
Frequently Asked Questions:
雖然得到了集成測試的覆蓋率文件,但是需要應用服務器上的類文件才能產(chǎn)出相應的覆蓋率報告杰捂,如果類文件是其他 JVM 編譯的社露,產(chǎn)出的報告覆蓋率是 0%。
有 2 種方法可以得到覆蓋率文件所需的 class 文件:
1. 將應用服務部署的包(ear 或 war 或 jar)包下載下來之后解壓琼娘,即可得到對應的 class 文件峭弟;
2. 在前面做單元測試之后,可以將 class 文件打成一個 zip 包脱拼,然后上傳到服務器瞒瘸,最后在需要的時候去服務器上取。
修改好了熄浓,那么我們來測試一下情臭,終端進入 build.xml 所在的目錄,執(zhí)行:ant dump 或者 ant dump -buildfile [yourPath/]build.xml
成功之后赌蔑,接下來就是 Jenkins 集成 jacoco 實現(xiàn)代碼覆蓋率俯在,詳見:
Jenkins + Jacoco持續(xù)集成代碼覆蓋率
是不是只有上面的這一種方式呢?當然不是娃惯!
第二種方式(不推薦):
JAVA_OPTS="-javaagent:[yourPath/]jacocoagent.jar=destfile=[storagePath/]jacoco.exec
同樣是加載 cataline.sh 文件中跷乐,除了獲取報告的方式上面的不一樣之前,其余步驟都一樣
獲取報告:
功能測試或者接口自動化后趾浅,需要獲取報告的話愕提,需關閉 Tomcat 獲取結果文件 `jacoco.exec`,使用 kill [PID]皿哨,之后到你保存的路徑下就能看到 `jacoco.exec` 文件(切記不要使用 kill -9 [PID]浅侨,否則不能生成結果)
不推薦這種方式的理由:如果使用這種方式的話,不好做持續(xù)集成证膨,因為 jenkins 服務器基本上都是和部署代碼的服務器分開的如输,所以要從遠程服務器取結果的話還是選擇上面的方式
Q:那現(xiàn)在可能又有同學會問,這個報告只能在 `Jenkins` 上面生成嗎央勒?
A:當然也可以在本地生成了不见,附上代碼,如下??
<?xml version="1.0" encoding="UTF-8"?>
<project name="Jacoco" xmlns:jacoco="antlib:org.jacoco.ant" default="jacoco">
<!--Jacoco 的安裝路徑-->
<property name="jacocoantPath" value="[yourPath/]jacocoant.jar"/>
<!--最終生成 .exec 文件的路徑订歪,Jacoco 就是根據(jù)這個文件生成最終的報告的-->
<property name="jacocoexecPath" value="[yourPath/]jacoco.exec"/>
<!--生成覆蓋率報告的路徑-->
<property name="reportfolderPath" value="[storageReportPath]"/>
<!--遠程 Tomcat 服務的 ip 地址-->
<property name="server_ip" value="100.44.44.144"/>
<!--前面配置的遠程 Tomcat 服務打開的端口脖祈,要跟上面配置的一樣-->
<property name="server_port" value="8044"/>
<!--源代碼路徑-->
<property name="checkOrderSrcPath" value="[srcPath]" />
<!--.class 文件路徑-->
<property name="checkOrderClasspath" value="[classPath]" />
<!--讓 ant 知道去哪兒找 Jacoco-->
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="${jacocoantPath}" />
</taskdef>
<!--dump 任務:
根據(jù)前面配置的 ip 地址肆捕,和端口號刷晋,
訪問目標 Tomcat 服務,并生成 .exec 文件。-->
<target name="dump">
<jacoco:dump address="${server_ip}" reset="false" destfile="${jacocoexecPath}" port="${server_port}" append="true"/>
</target>
<!--jacoco 任務:
根據(jù)前面配置的源代碼路徑和 .class 文件路徑眼虱,
根據(jù) dump 后喻奥,生成的 .exec 文件,生成最終的 html 覆蓋率報告捏悬。-->
<target name="report">
<delete dir="${reportfolderPath}" />
<mkdir dir="${reportfolderPath}" />
<jacoco:report>
<executiondata>
<file file="${jacocoexecPath}" />
</executiondata>
<structure name="JaCoCo Report">
<group name="Check Order related">
<classfiles>
<fileset dir="${checkOrderClasspath}">
<!-- 過濾不必要的文件 -->
<exclude name="**/R.class"/>
<exclude name="**/R$*.class"/>
<exclude name="**/*$ViewInjector*.*"/>
<exclude name="**/BuildConfig.*"/>
<exclude name="**/Manifest*.*"/>
</fileset>
</classfiles>
<sourcefiles encoding="UTF-8">
<fileset dir="${checkOrderSrcPath}" />
</sourcefiles>
</group>
</structure>
<html destdir="${reportfolderPath}" encoding="UTF-8" />
<csv destfile="${reportfolderPath}/coverage-report.csv" encoding="UTF-8"/>
<xml destfile="${reportfolderPath}/coverage-report.xml" encoding="UTF-8"/>
</jacoco:report>
</target>
</project>
Jacoco 收集單元測試代碼覆蓋率
-
pom.xml
配置plugin
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.7.201606060606</version>
<configuration>
<!--指定生成 .exec 文件的存放位置-->
<destFile>target/coverage-reports/jacoco-unit.exec</destFile>
<!--Jacoco 是根據(jù) .exec 文件生成最終的報告撞蚕,所以需指定 .exec 的存放路徑-->
<dataFile>target/coverage-reports/jacoco-unit.exec</dataFile>
</configuration>
<executions>
<execution>
<id>jacoco-initialize</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>jacoco-site</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
Demo 工程下載
- 下載之后解壓,直接進入工程目錄过牙,運行
mvn test
甥厦,接著你將看到如下圖所示的文件
image.png
其中 jacoco-unit.exec 是二進制文件,就不多說了寇钉,而 index.html 就是代碼覆蓋率報告刀疙,如下圖??
Tip:
綠色部分:完全覆蓋
黃色部分:條件覆蓋
紅色部分:未覆蓋
- 合并集成測試代碼覆蓋率和單元測試代碼覆蓋率,
build.xml
代碼如下??
<?xml version="1.0" encoding="UTF-8"?>
<project name="Jacoco" xmlns:jacoco="antlib:org.jacoco.ant" default="jacoco">
<property name="baseDir" value="[yourExecFilePath]"/>
<property name="jacocoantPath" value="[yourPath/]jacocoant.jar"/>
<property name="allJacocoexecPath" value="./jacoco-all.exec"/>
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="${jacocoantPath}" />
</taskdef>
<target name="merge">
<jacoco:merge destfile="${allJacocoexecPath}">
<fileset dir="${baseDir}" includes="*.exec"/>
</jacoco:merge>
</target>
</project>
只要將這份 build.xml 放在代碼的根目錄下扫倡,執(zhí)行 ant merge 就可將所有以 .exec 文件合并谦秧,重新生成名為 jacoco-all.exec 的二進制文件,當然也可以將文章中的兩份 build.xml 文件合并撵溃,代碼如下??
<?xml version="1.0" encoding="UTF-8"?>
<project name="Jacoco" xmlns:jacoco="antlib:org.jacoco.ant" default="jacoco">
<property name="jacocoantPath" value="[yourpath/]jacocoant.jar"/>
<property name="baseDir" value="[yourExecFilePath]"/>
<property name="integrationJacocoexecPath" value="./jacoco-integration.exec"/>
<property name="allJacocoexecPath" value="./jacoco-all.exec"/>
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="${jacocoantPath}" />
</taskdef>
<target name="dump">
<jacoco:dump address="100.44.44.144" port="8044" reset="true" destfile="${integrationJacocoexecPath}" append="false"/>
</target>
<target name="merge">
<jacoco:merge destfile="${allJacocoexecPath}">
<fileset dir="${baseDir}" includes="*.exec"/>
</jacoco:merge>
</target>
</project>
分別執(zhí)行:
`ant dump` & `ant merge`
or
`ant dump -buildfile [yourpath/]build.xml` & `ant merge -buildfile [yourpath/]build.xml`
這樣生成的代碼覆蓋率報告中既包含集成測試代碼覆蓋率疚鲤,又包含單元測試代碼覆蓋率的報告
將 .exec 文件合并之后,參照上文中提到的
Jenkins + Jacoco 持續(xù)集成代碼覆蓋率 這篇文章缘挑,將它與 Jenkins 集成集歇。當然還可以借助于 Sonar 將靜態(tài)代碼檢查的數(shù)據(jù)與代碼覆蓋率同步到 SonarQube 平臺,詳見:
SonarQube & SonarQube Scanner
如果在閱讀或者實踐的過程中遇到什么問題语淘,歡迎在下方評論