通過JaCoCo統計接口測試代碼覆蓋率
需求:統計微服務接口測試的代碼覆蓋率
- JaCoCo的ant與maven方法都是在編譯期對單元測試的覆蓋率統計
- JaCoCo的可以開啟一個agent服務收集運行過程中的代碼執行覆蓋率。
主要會用到jacoco 的兩個功能:agent和cli
覆蓋率收集
1. 收集方式
鑒於接口測試是在微服務啟動后運行的測試,所以在選用第二種agent的測試,會有兩個比較麻煩的地方(列出了自己比較笨拙的解決方法)
- 源碼獲取:去git上再拉取一遍。
- 編譯后字節碼獲取:按照測試環境構建再統計過程中再構建一遍。
統計覆蓋率過程基本如:
- 微服務啟動,同時啟動agent收集覆蓋率
- 接下來基本都是jenkins任務需要做的
- git獲取項目源碼
- 使用構建工具編譯源碼
- 使用cli工具獲取JaCoCo覆蓋率統計文件
- 使用cli工具根據exec文件生成覆蓋率報告
2. JaCoCo使用
參考jacoco官方使用文檔:官方文檔索引、agent幫助文檔、cli幫助文檔
- JaCoCo官方給出了3種收集覆蓋率文件的方式:file、tcpserver、tcpclient。
在JVM虛擬機啟動時加入參數: -javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2] , 實例如下:
# Example: 輸出到文件,在JVM終止時,執行數據被寫入本地文件。接口測試不會考慮這種情況(單測一般是用這種)
# 1. 服務終止影響整個測試環境。
# 2. 有些跨環境的服務,獲取微服務所在機器上的文件也比較麻煩。
-javaagent:~/jacoco-0.8.5/lib/jacocoagent.jar=includes=*,output=file,append=true,destfile=~/jacoco-0.8.5/jacoco.exec
# Example: 輸出到tcpserver,使用工具連接到JVM,獲取dump數據。使用這種方式不需要停止jvm,也直接可以通過網絡傳輸。但是遠程連接沒有任何身份驗證機制,所以生產環境一定要確保只有信任的人可訪問jacoco地址。配合jacoco cli獲取數據。
-javaagent:~/jacoco-0.8.5/lib/jacocoagent.jar=includes=*,output=tcpserver,append=true,address=127.0.0.1,port=6301
# 列出部分參數,
# includes:插樁的代碼類名列表,使用:分割,也可以使用*和?匹配,但是不考慮性能的情況下一般不需要使用。默認為*
# output:用於寫入coverage數據的輸出方法。有效選項包括:file、tcpserver、tcpclient、none
# append:如果文件已經存在則追加到已存在文件中,如果為false則替換
# destfile:輸出exec文件的路徑
# address:配合tcpserver來指定對外開放的jacoco訪問地址。如果配置的127.0.0.1或localhost則只能本地訪問dump數據
# port:配合tcpserver來指定對外開放的jacoco端口。端口不能被占用
- JaCoCo命令行界面:命令行界面提供了基本的操作,基本能滿足接口覆蓋率報告的生成;dump數據與生成報告都使用cli。
# Example:獲取jacoco server對外開放地址的數據。
# --address jacoco tcpserver地址,網絡要通不然啥都白搭。
# --port jacoco tcpserver端口
# --destfile dump數據存儲位置
java -jar ${jacoco_home}/lib/jacococli.jar dump --address ${address} --port ${port} --destfile ${destfile}
# Example:使用獲取到的exec文件生成覆蓋率報告。
# --classfiles 必須指定,源碼編譯后target目錄文件。(這也是jenkins任務在單獨拉取源碼執行編譯的原因)
# --sourcefiles 源碼,非必須項。不指定無法查看代碼執行詳細情況。
# --html html報告生成目錄
java -jar ${jacoco_home}/lib/jacococli.jar report ${destfile} --classfiles ${classfiles} --sourcefiles ${sourcefiles} --html reportdir
接下來創建一個項目實驗一下
Sprint Boot測試項目
1. 創建項目



2. 工程結構
src
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── demo/
│ │ ├── JacocoDemoApplication.java
│ │ └── controller/
│ │ └── CountController.java 測試controller
│ └── resources/
│ ├── application.properties
│ ├── static/
│ └── templates/
└── test/
└── java/
└── com/
└── example/
└── demo/
└── JacocoDemoApplicationTests.java
3. CountController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
public class CountController {
@RequestMapping(value = "/test1", method = RequestMethod.POST)
@ResponseBody
public boolean caseCount(@RequestBody Map<String, Integer> params) {
if (params.get("count") > 0) {
return true;
} else {
return false;
}
}
@RequestMapping(value = "/test2", method = RequestMethod.POST)
@ResponseBody
public boolean caseCount1(@RequestBody Map<String, Integer> params) {
if (params.get("count") > 0) {
return true;
} else {
return false;
}
}
}
4. 上傳代碼到github


# 進入項目目錄,初始化項目
git init
# 新增修改文件。
git add .
# 按照上一步提示使用命令設置本項目的賬戶(也可以加--global設置全局)
git config --local user.email '***'
# 提交本次新增內容
git commit -m 'jacoco 代理統計覆蓋率demo代碼'
# 設置代碼庫地址
git remote add origin https://github.com/hzhang123/jacoco-demo.git
# 獲取項目初始化的README.md文件
git pull --rebase origin master
# 上傳到github。 可能會讓輸入用戶名密碼
git push -u origin master
覆蓋率統計測試
我這里全部是本地項目,所以都用的本地127.0.0.1
1. 啟動項目
- 添加參數啟動項目

- postman 發送測試請求


2. jenkins任務
- 添加流水線任務

- 添加string參數

- Pipeline script
只是測試,所以pipeline腳本中有有一些參數我直接寫死了,比如:GIT_BRANCH、CODE_REPO、jacoco_home、classfiles、sourcefiles,可以根據不同項目配置一下構建參數。
#!/usr/bin/env groovy
pipeline {
agent any
environment {
// 如果scala構建使用sbt,jenkins兼容不太好需要environment中拼接工具地址
SBT_HOME = tool name: 'sbt1.3.0', type: 'org.jvnet.hudson.plugins.SbtPluginBuilder$SbtInstallation'
PATH = "${env.SBT_HOME}/bin:${env.PATH}"
// 可以添加構建參數用來指定獲取分支等信息
GIT_BRANCH = "master"
CODE_REPO = "https://github.com/hzhang123/jacoco-demo.git"
}
tools {
// 引入tools中配置的工具
maven "maven3.6.1"
jdk 'jdk1.8.0_231'
}
stages {
stage('Clone & Build') {
steps {
deleteDir()
git branch: env.GIT_BRANCH, url: CODE_REPO
// 如果有Phabricator & Arcanist工具可以配合review與打patch
// script {
// diffs = DIFFS.trim().toUpperCase().split("(\\s+|\\s*,\\s*)")
// for (i = 0; i < diffs.length; i++) {
// id = diffs[i];
// if (id.trim().length() > 0) {
// sh "arc patch ${id}"
// sh "git checkout ${GIT_BRANCH}"
// sh "git merge arcpatch-${id}"
// }
// }
// }
sh "mvn clean package"
}
}
stage('Exec & Report') {
steps {
sh ''' # JaCoCo依賴在jenkins上的地址 jacoco_home="/Users/growingio/developments/tools/jacoco-0.8.5" # ---------------- # dump tcp端口數據 # ---------------- address=`echo ${server_addr} | awk -F: '{print $1}'` port=`echo ${server_addr} | awk -F: '{print $2}'` destfile=target/JacocoDemo.exec java -jar ${jacoco_home}/lib/jacococli.jar dump --address ${address} --port ${port} --destfile ${destfile} # 生成報告 classfiles="target/classes" sourcefiles="src/main/java" java -jar ${jacoco_home}/lib/jacococli.jar report ${destfile} --classfiles ${classfiles} --sourcefiles ${sourcefiles} --html report '''
}
}
stage('publishHTML & Clean Workspace') {
steps {
publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: false, reportDir: "report", reportFiles: 'index.html', reportName: 'HTML Report', reportTitles: ''])
cleanWs()
}
}
}
}
3. 構建報告
詳細參數可以查看官方文檔:代碼覆蓋率參數文檔
Instructions (C0 Coverage):JaCoCo 計算的最小單位就是字節碼指令。指令覆蓋率表明了在所有的指令中指令執行的覆蓋率。
Branches (C1 Coverage):JaCoCo還計算所有的for與if分支覆蓋率,異常處理不在分支計算范圍內。黃色(部分覆蓋)、紅色(未覆蓋)、綠色(全部覆蓋)
Cyclomatic Complexity:圈復雜度
Line:可能一行會被編譯為多個指令,所以在源碼高亮顯示每行代碼的情況。黃色(部分覆蓋)、紅色(未覆蓋)、綠色(全部覆蓋)
Methods:每個方法至少包含一個指令,當改指令被執行時認為方法被執行。
Classes:每個類至少包含一個方法,當該方法被執行時認為類被執行。

