文章目录
背景一、准备工作 - 生成exec文件二、准备工作 - 引入依赖
三、利用jenkins-jacoco插件源码收集覆盖率结果四、生成jacoco报告文件,聚合多模块附赠相关知识点
背景
持续集成项目,完善单元测试的能力 获取单元测试通过率(mvn test 获取surefire report),获取代码覆盖率(jacoco) 本次实践基于jenkins环境 以及 jenkins - jacoco插件 收集覆盖率统计结果跟生成jacoco报告文件无关,生成jacoco聚合报告文件不依赖jenkins环境,请各位有需要的看官认真查看 maven单测已完成测试,此插件方案应该适用于java语言 gradle、android(前提已生成exec文件)
一、准备工作 - 生成exec文件
maven插件的使用方式: - 在pom.xml文件里配置插件 - 使用命令行加载 pom文件配置网上很多资料,跳过了,也不是此文关注点。因为是持续集成,需要给各种项目做编译,并不只是自己的,因此不能改变用户的代码情况下,使用命令行形式为佳。
命令行形式: - maven测试命令:mvn clean test - 配置命令:-Dautoconfig.skip=true -Dmaven.test.skip=false -Dmaven.test.failure.ignore=true - maven的jacoco插件:org.jacoco:jacoco-maven-plugin:0.8.5:prepare-agent - 运行单元测试并生成exec:
mvn org
.jacoco
:jacoco
-maven
-plugin
:0.8.5:prepare
-agent clean test
-Dautoconfig
.skip
=true
-Dmaven
.test
.skip
=false
-Dmaven
.test
.failure
.ignore
=true
附: - gradle、android的exec文件生成:在项目根目录放入一个.gradle文件,命令行加载编译 - ./gradlew --init-script init.gradle clean build 不是专业的就不细讲啦~
二、准备工作 - 引入依赖
<dependency
>
<groupId
>org
.jacoco
</groupId
>
<artifactId
>jacoco
-maven
-plugin
</artifactId
>
<version
>0.8.5</version
>
</dependency
>
jacoco
-maven
-plugin里包含了org
.jacoco
.core,org
.jacoco
.report都是必要的。
因为是在jenkins插件开发,开发中遇到过依赖冲突问题,困扰几天的奇怪的bug最终也是依赖问题,认真对待依赖很重要
三、利用jenkins-jacoco插件源码收集覆盖率结果
jacoco插件的JacocoPublisher类 perform方法为执行入口 一番分析后提取核心代码占为己有*-* 其实以下代码都是jacoco插件源码的抽取,方便自己开发插件,扩展功能,如jacoco增量覆盖率检测,自定义前端样式 写文章时候才发现很多类都是基于jenkins,普通项目不一定很好移植,本地@test可行估计也是准备了一定的环境,但是还是觉得应该能成功的
public class JacocoCore
{
private String rootDir
= "/data/myapp";
private String execsPattern
= "**/*.exec";
private String classesPattern
= "**/classes";
private String sourcesPattern
= "**/src/main/java";
private String inclusionPattern
= "";
private String exclusionPattern
= "**/*Test*.class";
private String sourceInclusionPattern
= "**/*.java,**/*.groovy,**/*.kt,**/*.kts";
public List
<FilePath
> collectExecFilesByPattern(String pattern
){
return Arrays
.asList(workspace
.list(pattern
));
}
public
void getJacocoResultData() {
1. 设定一个收集文件用的根目录
JacocoReportDir reportDir
= new
JacocoReportDir(rootDir
);
2. 通过通配符收集符合条件的目录路径,如app
/amodule
/target
/classes
,app
/bmodule
/target
/classes
FilePath
[] machedClassDirs
= workspace
.act(new JacocoPublisher
.ResolveDirPaths(classesPattern
))
3. 将classes目录下所有的符合条件
"**/*.class"的文件全部拷贝到reportDir
.getClassesDir()目录下
for (FilePath dir
: matchedClassDirs
) {
int copied
= reportDir
.saveClassesFrom(dir
, "**/*.class")
}
logger
.info("copy to " + reportDir
.getClassesDir().getAbsolutePath())
4. 同样的将source文件拷贝
FilePath
[] machedSrcDirs
= workspace
.act(new JacocoPublisher
.ResolveDirPaths(sourcesPattern
))
for (FilePath dir
: matchedSrcDirs
) {
int copied
= reportDir
.saveSourcesFrom(dir
, sourceInclusionPattern
, sourceExclusionPattern
)
}
5. 设定分析的目标文件及排除文件
if(inclusionPattern
!= null
) {
String
[] includes
= inclusionPattern
.split("\\s*,\\s8")
}
if(exclusionPattern
!= null
) {
String
[] excludes
= exclusionPattern
.split("\\s*,\\s8")
}
6. 设定扩展参数
JacocoHealthReportThresholds healthReports
= new
JacocoHealthReportThresholds(0,...0);
7. 构造jacoco程序
final JacocoBuildAction action
= JacocoBuildAction
.load(healthReports
, listener
, reportDir
, includes
, excludes
);
action
.getThresholds().ensureValid();
8. 获取覆盖率报告结果
final CoverageReport result
= action
.getResult();
logger
.info("coverage of class:" + result
.getClassCoverage().getPercentageFloat() + "%")
...其他指标
备注:instructionCoverage就是sonar显示的汇总后的总覆盖率指标,getPercentageFloat源码是已经乘
100的
logger
.info("coverage :" + new
DecimalFormat("0.00%").format(result
.getInstructionCoverage().getPercentageFloat() / 100))
}
四、生成jacoco报告文件,聚合多模块
生成报告文件很简单,maven jacoco插件官网就有示例,已放在参考连接 此时生成的报告是每个模块都独立的,报告首页是这样的:
那么如何将多个模块的报告都聚合到一个文件里呢? 怎么才能获得下图带模块文件夹的jacoco报告呢?
有一种常规的生成方式:新建一个空module,在pom文件里写jacoco插件配置 具体也放在参考链接啦~ 综合其他两个附件内容及一顿乱测后 终于悟出了生成jacoco聚合报告的真理!!
public
void createReport(String appName
, List
<File
> execFiles
){
JacocoReportGenerator jacocoReportGenerator
= new
JacocoReportGenerator();
List
<IReportVisitor
> visitors
= new ArrayList
<>();
Map
<IbundleCoverage
, File
> coverageSourceDirMap
= new HashMap
<>();
File reportSaveDir
= new
File("/data/jacoco-reports");
for(File execFile
: execFiles
){
File classesDir
= new
File(execFile
.getParent
, "classes");
File sourcesDir
= new
File(execFile
.getParentFile().getParent() + "/src/main/java");
File moduleDir
= execFile
.getParentFile().getParentFile();
jacocoReportGenerator
.setSourceInfo(moduleDir
.getName
, execFile
, classesDir
, sourcesDir
, reportSaveDir
)
jacocoReportGenerator
.addToBundleCoverageList(coverageSourceDirMap
);
jacocoReportGenerator
.createCoverageList(visitors
);
}
jacocoReportGenerator
.createReports(coverageSourceDirMap
, visitors
, appName
);
}
public class ReportGenerator
{
private final String title
;
private final File execFile
;
private final File classesDirectory
;
private final File sourceDirectory
;
private final File reportDirectory
;
private ExecFileLoader execFileLoader
;
public
ReportGenerator(){
}
public
void addToBundleCoverageList(Map
<IbundleCoverage
, File
> coverageSourceDirMap
) throws IOException
{
loadExecutionData();
bundleCoverage
= analyzeStructure();
coverageSourceDirMap
.put(bundleCoverage
, sourceDirectory
);
}
public
void createCoverageList(List
<IReportVisitor
> visitors
) throws IOException
{
for(int i
=0; i
<coverageList
.size(); i
++)
{
HTMLFormatter htmlFormatter
= new
HTMLFormatter();
IReportVisitor visitor
= htmlFormatter
.createVisitor(new
FileMultiReportOutput(reportDirectory
));
visitor
.visitInfo(execFileLoader
.getSessionInfoStore().getInfos(),
execFileLoader
.getExecutionDataStore().getContents());
visitors
.add(visitor
);
}
}
public
void createReportFromList(Map
<IbundleCoverage
, File
> coverageSourceDirMap
, List
<IReportVisitor
> visitors
, String appName
) throws IOException
{
MultiReportVisitor mrv
= new
MultiReportVisitor(visitors
);
IReportGroupVisitor irgv
= mrv
.visitGroup(appName
);
for(Map
.Entry
<iBundleCoverage
, File
> iBundleCoverageFileEntry
: coverageSourceDirMap
.entrySet()){
irgv
.visitBundle(iBundleCoverageFileEntry
.getKey(), new
DirectorySorceFileLocator(iBundleCoverageFileEntry
.getValue(), "utf-8", 4));
}
mrv
.visitEnd();
}
private
void loadExecutionData() throws IOException
{
execFileLoader
= new
ExecFileLoader();
execFileLoader
.load(executionDataFile
);
}
private IBundleCoverage
analyzeStructure() throws IOException
{
final CoverageBuilder coverageBuilder
= new
CoverageBuilder();
final Analyzer analyzer
= new
Analyzer(
execFileLoader
.getExecutionDataStore(), coverageBuilder
);
analyzer
.analyzeAll(classesDirectory
);
return coverageBuilder
.getBundle(title
);
}
}
附赠相关知识点
附1: 根据生成的exec文件给其所在模块生成jacoco报告(在target/site目录下) 因为想要使用java代码生成报告并将每个模块的报告合成到文件夹形式的jacoco首页形式,所以此为附加信息提供参考,无该要求的可直接命令行生成并收集报告文件即可
mvn org
.jacoco
:jacoco
-maven
-plugin
:report
附2: - maven的surefire-report插件:surefire-report:report-only -DalwaysGenerateSurefireReport=false(没有测试用例的模块不生成html报告) - site配置:mvn site -DgenerateReports=false(给surefire-report生成css相关文件,由于所有的html报告都用的同一套css,所以可复用) 有样式的surefire-report应该是这样子的:
参考链接 pom文件形式生成jacoco聚合报告 官网jacoco报告生成样例 代码实现jacoco报告多模块聚合