写在最前:
本文主要讲解SonarQube实现对Java代码的集成覆盖率扫描,代码质量扫描。
其中,集成覆盖率分为单节点,集群两种模式,文内都有详解。
参考链接:
流程说明
本篇内容实际为4-7步,1-3步骤配置可以参考Jenkins发布配置
- Jenkins拉取被测项目,编译打包,发送到服务器并启动,此处在应用服务器JVM中添加了JacocoAgent,动态获取覆盖率
- Jenkins拉取被测代码和测试代码
被测代码:扫描java代码,扫描代码质量;扫描class类,和Ant获取到的覆盖代码行进行对比,生成覆盖率文件(能在SonarQube上展示覆盖率和每个代码文件的详细覆盖率)
测试代码:执行测试类,通过Ant获取覆盖率文件 - Ant
获取覆盖率文件,整合覆盖率文件(集群模式),生成xml报告(新版本SonarQube推荐扫描xml报告生成页面展示内容而不是直接获取到的原始覆盖率文件) - SonarScanner
扫描被测项目,获取代码质量报告
扫描Ant覆盖率报告:获取详细覆盖率结果,和扫描class类结合使用
扫描class文件,生成SonarQube覆盖率索引(如果不扫描只能看到一个覆盖率,扫描之后能看详细每个文件,每行覆盖了多少)
应用服务器配置
1. 安装Jacoco
在被测应用服务器上需要安装JaCoCo,参考环境版本和安装,直接将整个jacoco文件夹复制到应用服务器即可
2. 修改应用启动参数
项目启动jvm 参数中添加:注:下面参数必须添加在jvm参数最前面,否则可能不生效
-javaagent:jacocoagent.jar=includes=*,output=tcpserver,address=项目服务器本机ip,port=自定义端口,append=false
并且pom中不能配置跳过test,否则单元测试类不执行
分布式单节点环境覆盖率扫描配置
1. 被测项目(需要扫描的项目)配置
pom.xml 同级目录下添加sonar-project.properties文件, 内容如下:
#sonarqube key
sonar.projectKey=ncba_interface_account
#sonarqube ui job name
sonar.projectName=ncba_interface_account
#sonarqube job version
sonar.projectVersion=1.0.0
#more than one branch , set branch name
#sonar.branch.name=account-develop
#code language
sonar.language=java
#exclusion code file
#**/entity/**,*/*BO.java,
sonar.exclusions=**/service/detailedness/**,**/service/accounting/**,**/service/account/**,**/action/wallettransaction/**,**/action/saaquery/**
#more than one moudle,set this
#模块名,多个模块就配置多个,如果就一个可以省略
sonar.modules=account-api,account-web
#↓↓↓这个是目录名,对应↑↑↑↑↑,这里是模块↓↓↓↓↓↓↓代码路径
account-api.sonar.projectBaseDir=account-api
account-web.sonar.projectBaseDir=account-web
#code file path
sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.sourceEncoding=UTF-8
sonar.java.binaries=target/classes
#SonarQube username and password
sonar.login=user
sonar.password=password
#jacoco config
sonar.java.coveragePlugin=jacoco
#report file path
#这个路径是覆盖率文件生成覆盖率报告的位置,这个位置是用于本配置文件去读取的
sonar.coverage.jacoco.xmlReportPaths=/home/jenkins/interface_test/account/report.xml
git路径演示:
2. 测试项目配置
测试项目需要添加build.xml,是一个ant build文件,负责获取到远程覆盖率,并且在本地生成覆盖率文件,导出覆盖率报告,供上面配置的sonarqube读取。
build.xml 内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<project name="testCodeCoverage" xmlns:jacoco="antlib:org.jacoco.ant" default="run" ><!--默认执行run方法-->
<property name="result.exec.file" location="/home/jenkins/interface_test/account/jacocoremote_account.exec"/><!--存放覆盖率文件的位置-->
<property name="reportfolderPath" value="/home/jenkins/interface_test/account"/> <!--生成覆盖率报告的路径-->
<property name="result.jar.file" location="/home/jenkins/jacoco/lib/jacocoant.jar"/><!--指定的jacocoant.jar所在位置-->
<property name="result.exec.ip" value="192.168.0.1"/><!--部署被测项目的服务器ip-->
<property name="result.exec.port" value="40007"/><!--部署被测服务器开发的jacoco端口,ip和端口对应应用jvm启动参数的jacoco ip和端口-->
<!--此处取的jenkins workspace总路径,多余文件在下面排除-->
<property name="Model1JavaPath" value="/var/lib/jenkins/workspace/interface_test/account"/> <!--java代码-->
<property name="Model1ClassPath" value="/var/lib/jenkins/workspace/interface_test/account"/> <!--class代码-->
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="${jacoco.jar.file}"/>
</taskdef>
<target name="run"><!--设置总体调度器-->
<echo message="start..."/>
<echo message="dump..."/>
<antcall target="dump"/>
<echo message="report..."/>
<antcall target="report"/>
</target>
<!--生成覆盖率二进制文件-->
<target name="dump">
<!--删除历史报告-->
<delete dir="${reportfolderPath}" />
<mkdir dir="${reportfolderPath}" />
<jacoco:dump address="${result.exec.ip}" reset="false" destfile="${result.exec.file}" port="${result.exec.port}" append="flase"/>
</target>
<!--生成报告-->
<target name="report">
<jacoco:report>
<executiondata>
<!--jacoco.exec二进制文件位置-->
<file file="${result.exec.file}"/>
</executiondata>
<structure name="JaCoCo Report">
<!--api-->
<!-- <group name="account-api">
<sourcefiles encoding="UTF-8">
<fileset dir="${Model1ClassPath}/account-api/src/main/java">
<exclude name="*/*BO.java"/>
<exclude name="**/model/**"/>
<exclude name="**/Application.java"/>
<exclude name="**/entity/**"/>
<exclude name="**/dal/**"/>
<exclude name="**/common/**"/>
<exclude name="**/test_project/**"/>
</fileset>
</sourcefiles>
<classfiles>
<fileset dir="${Model1JavaPath}/account-api/target/classes"/>
</classfiles>
</group>-->
<!--web-->
<group name="account-web">
<sourcefiles encoding="UTF-8">
<fileset dir="${Model1JavaPath}/account-web/src/main/java">
<exclude name="*/*BO.java"/>
<exclude name="**/model/**"/>
<exclude name="**/Application.java"/>
<exclude name="**/entity/**"/>
<exclude name="**/dal/**"/>
<exclude name="**/common/**"/>
<exclude name="**/test_project/**"/>
</fileset>
</sourcefiles>
<classfiles>
<fileset dir="${Model1ClassPath}/WEB-INF/classes"/>
</classfiles>
</group>
</structure>
<!--生成报告格式,html,csv,xml-->
<html destdir="${reportfolderPath}" encoding="utf-8"/>
<csv destfile="${reportfolderPath}/report.csv"/>
<xml destfile="${reportfolderPath}/report.xml"/>
</jacoco:report>
</target>
</project>
3. Jenkins 配置
关键配置 :
上面几步基本都是固定写法,不用动
重点说明:
说明1. 我将覆盖率文件存在了/home/jenkins 目录下,jenkins workspace 只支持相对路径,娶不到 覆盖率文件,所以软连接过来
说明2. 将打好的war包移动到workspace根下并解压,该操作为必须,多次测试发现,如果不用mvn package打包,只用mvn compline ,实际上在jacoco 上,他会认为 编译方式所生成的classes文件的classID不相同,认为是不同的class文件,简单来说,就是覆盖率结果为0(部分情况可能不为0,因为有mvn私服模块是提前打包,可能打包方式相同,不理解的忽略这段话)
说明3. 此处是用于自动导出美化后的测试结果文件,暂时忽略
其他说明. 如果项目有单测,集群集成测试,单节点集成测试
sonar扫描配置文件起名请不要用sonar-project.properties
因为jenkins如果有叫sonar-project.properties的文件,直接就使用这个文件,即便配置了其他文件名也不好使
效果图:图中显示错误是 代码问题,忽略
分布式集群环境下覆盖率扫描配置
和单节点相比,不同的点就是涉及到服务分发,那么覆盖率分散的问题,我们需要做的就是覆盖率整合。
应用服务器配置
配置和上面无变化
sonar-project.properties文件配置
只改了下覆盖率文件路径
#sonarqube key
sonar.projectKey=ncba_interface_account
#sonarqube ui job name
sonar.projectName=ncba_interface_account
#sonarqube job version
sonar.projectVersion=1.0.0
#more than one branch , set branch name
#sonar.branch.name=account-develop
#code language
sonar.language=java
#exclusion code file
#**/entity/**,*/*BO.java,
sonar.exclusions=**/service/detailedness/**,**/service/accounting/**,**/service/account/**,**/action/wallettransaction/**,**/action/saaquery/**
#more than one moudle,set this
sonar.modules=account-api,account-web
account-api.sonar.projectBaseDir=account-api
account-web.sonar.projectBaseDir=account-web
#code file path
sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.sourceEncoding=UTF-8
sonar.java.binaries=target/classes
#SonarQube username and password
sonar.login=user
sonar.password=password
#jacoco config
sonar.java.coveragePlugin=jacoco
#report file path
sonar.coverage.jacoco.xmlReportPaths=/home/jenkins/interface_test/app/acm/report.xml
build.xml 文件配置
以三节点为例
<?xml version="1.0" encoding="UTF-8" ?>
<project name="testCodeCoverage" xmlns:jacoco="antlib:org.jacoco.ant" default="run" ><!--默认执行run方法-->
<property name="result.exec.file.dir" location="/home/jenkins/interface_test/account/"/><!--存放临时覆盖率文件的位置-->
<property name="result.exec.file1" location="/home/jenkins/interface_test/account/jacocoremote_account.exec1"/><!--存放临时覆盖率文件的位置-->
<property name="result.exec.file2" location="/home/jenkins/interface_test/account/jacocoremote_account.exec2"/><!--存放临时覆盖率文件的位置-->
<property name="result.exec.file3" location="/home/jenkins/interface_test/account/jacocoremote_account.exec3"/><!--存放临时覆盖率文件的位置-->
<property name="result.exec.file" location="jacocoremote_account.exec"/><!--存放最终覆盖率文件的位置-->
<property name="reportfolderPath" value="/home/jenkins/interface_test/app/acm"/> <!--生成覆盖率报告的路径-->
<property name="result.jar.file" location="/home/jenkins/jacoco/lib/jacocoant.jar"/><!--指定的jacocoant.jar所在位置-->
<property name="result.exec.ip1" value="192.168.1.1"/><!--部署被测项目的服务器ip-->
<property name="result.exec.ip2" value="192.168.1.2"/><!--部署被测项目的服务器ip-->
<property name="result.exec.ip3" value="192.168.1.3"/><!--部署被测项目的服务器ip-->
<property name="result.exec.port" value="40007"/><!--部署被测服务器开发的jacoco端口-->
<!--此处取的jenkinsworkspace总路径,多余文件在下面排除-->
<property name="Model1JavaPath" value="/var/lib/jenkins/workspace/interface_test/account"/> <!--模块1java代码-->
<property name="Model1ClassPath" value="/var/lib/jenkins/workspace/interface_test/account"/> <!--模块1class代码-->
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="${jacoco.jar.file}"/>
</taskdef>
<target name="run"><!--设置总体调度器-->
<echo message="start..."/>
<echo message="dump1..."/>
<antcall target="dump1"/>
<echo message="dump2..."/>
<antcall target="dump2"/>
<echo message="dump3..."/>
<antcall target="dump3"/>
<echo message="merge..."/>
<antcall target="merge"/>
<echo message="report..."/>
<antcall target="report"/>
</target>
<!--生成覆盖率二进制文件-->
<target name="dump1">
<delete dir="${reportfolderPath}" />
<mkdir dir="${reportfolderPath}" />
<jacoco:dump address="${result.exec.ip1}" reset="false" destfile="${result.exec.file1}" port="${result.exec.port}" append="flase"/>
</target>
<target name="dump2">
<jacoco:dump address="${result.exec.ip2}" reset="false" destfile="${result.exec.file2}" port="${result.exec.port}" append="flase"/>
</target>
<target name="dump3">
<jacoco:dump address="${result.exec.ip3}" reset="false" destfile="${result.exec.file3}" port="${result.exec.port}" append="flase"/>
</target>
<!--合并覆盖率文件-->
<target name="merge">
<jacoco:merge destfile="${result.exec.file}">
<fileset dir="${result.exec.file.dir}" includes="**/*.exec*"/>
</jacoco:merge>
</target>
<!--生成报告-->
<target name="report">
<!--删除历史报告-->
<jacoco:report>
<executiondata>
<!--jacoco.exec二进制文件位置-->
<file file="${result.exec.file}"/>
</executiondata>
<structure name="JaCoCo Report">
<!--web-->
<group name="account-web">
<sourcefiles encoding="UTF-8">
<fileset dir="${Model1JavaPath}/account-web/src/main/java">
<exclude name="*/*BO.java"/>
<exclude name="**/model/**"/>
<exclude name="**/Application.java"/>
<exclude name="**/entity/**"/>
<exclude name="**/dal/**"/>
<exclude name="**/common/**"/>
<exclude name="**/test_project/**"/>
</fileset>
</sourcefiles>
<classfiles>
<fileset dir="${Model1ClassPath}/WEB-INF/classes"/>
</classfiles>
</group>
</structure>
<!--生成报告格式,html,csv,xml-->
<html destdir="${reportfolderPath}" encoding="utf-8"/>
<csv destfile="${reportfolderPath}/report.csv"/>
<xml destfile="${reportfolderPath}/report.xml"/>
</jacoco:report>
</target>
</project>
jenkins配置
只需要更改这一步,其他完全不变
OK,大功告成!