代码分析、单元测试、覆盖率统计
单测代码覆盖率统计实战
Maven 项目代码覆盖率统计
jacococli 工具
集成代码覆盖率统计实战
Maven 项目代码分析
代码 Bug 分析
代码复杂度分析
代码质量门禁
SonarQube 与持续集成结合
Applied Software Measurement, Capers Jones, 1996
微软
sonarqube 技术架构图
sonarqube 项目分析案例
典型 bug 案例
ceshiren.com
霍格沃兹测试开发
while b ≠ 0
if a > b
a := a − b
else
b := b − a
return a
public class ControlFlow {
public static void main(String[] args) {
int a = 1;
while (a < 10) {
a++;
if (a < 5) {
System.out.println("a<5");
} else {
System.out.println("a>=5");
}
}
}
}
object ControlFlow {
@JvmStatic
fun main(args: Array<String>) {
var a = 1
while (a < 10) {
a++
if (a < 5) {
println("a<5")
} else {
println("a>=5")
}
}
}
}
Compiled from "ControlFlow.java"
public class com.hogwarts.instrument.ControlFlow {
public com.hogwarts.instrument.ControlFlow();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpge 38
8: iinc 1, 1
11: iload_1
12: iconst_5
13: if_icmpge 27
16: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
19: ldc #3 // String a<5
21: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
24: goto 2
27: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
30: ldc #5 // String a>=5
32: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: goto 2
38: return
}
目前大部分公司都采用了 sonarqube,它的概念和技术指标已经成为行业事实上的标准
#通用部署
docker run -d \
--name sonarqube_temp \
-p 9000:9000 -p 9092:9092 \
sonarqube
#mac部署
docker run -d \
--name sonarqube_temp \
-p 9000:9000 -p 9092:9092 \
--platform linux/x86_64 \
-e SONAR_SEARCH_JAVAADDITIONALOPTS="-Dbootstrap.system_call_filter=false" \
sonarqube
#创建容器网络
docker network create sonarqube
#创建数据库容器实例
docker run -d \
--name sonarqube_postgres \
--network sonarqube \
-e POSTGRES_USER=sonarqube \
-e POSTGRES_PASSWORD=sonarqube \
-e PGDATA=/var/lib/postgresql/data/pgdata \
-v $PWD/postgresql_data:/var/lib/postgresql/data \
postgres
#创建sonarqube容器实例,可以通过-Xms4g -Xmx4g增加下ES的堆内存大小
#mac环境需要加 -e SONAR_SEARCH_JAVAADDITIONALOPTS="-Dbootstrap.system_call_filter=false"
docker run -d \
--name sonarqube_hogwarts \
-p 9000:9000 -p 9092:9092 \
--network sonarqube \
-e SONARQUBE_JDBC_USERNAME=sonarqube \
-e SONARQUBE_JDBC_PASSWORD=sonarqube \
-e SONARQUBE_JDBC_URL="jdbc:postgresql://sonarqube_postgres/sonarqube" \
-e SONAR_SEARCH_JAVAADDITIONALOPTS="-Xms2g -Xmx2g" \
-e SONARQUBE_WEB_JVM_OPTS="-Xms1g -Xmx1g" \
-v $PWD/sonarqube_data:/opt/sonarqube/data \
-v $PWD/sonarqube_extensions:/opt/sonarqube/extensions \
-v $PWD/sonarqube_logs:/opt/sonarqube/logs \
sonarqube
仅用于学习与参考
https://sonarqube.stuq.ceshiren.com/
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
#临时方案
sysctl -w vm.max_map_count=262144
#永久方案 添加一行到/etc/sysctl.conf
vm.max_map_count=262144
java.lang.UnsupportedOperationException: seccomp unavailable: CONFIG_SECCOMP not compiled into kernel, CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER are needed
#创建sonarqube容器实例
docker run -d \
--name sonarqube_hogwarts \
--platform linux/x86_64 \
-p 9000:9000 -p 9092:9092 \
--network sonarqube \
--user $(id -u):$(id -g) \
-e SONARQUBE_JDBC_USERNAME=sonarqube \
-e SONARQUBE_JDBC_PASSWORD=sonarqube \
-e SONARQUBE_JDBC_URL="jdbc:postgresql://sonarqube_postgres/sonarqube" \
-e SONAR_SEARCH_JAVAADDITIONALOPTS="-Dbootstrap.system_call_filter=false -Xms1g -Xmx1g" \
-e SONARQUBE_WEB_JVM_OPTS="-Xms1g -Xmx1g" \
--ulimit nofile=524288:524288 \
-v $PWD/sonarqube_data:/opt/sonarqube/data \
-v $PWD/sonarqube_extensions:/opt/sonarqube/extensions \
-v $PWD/sonarqube_logs:/opt/sonarqube/logs \
sonarqube
-e SONAR_SEARCH_JAVAADDITIONALOPTS="-Xms1g -Xmx4g"
-e SONAR_SEARCH_JAVAADDITIONALOPTS="-Dbootstrap.system_call_filter=false -Xms1g -Xmx4g"
SQ_HOST=http://127.0.0.1:9000
SQ_TOKEN=你自己sonarqube环境的TOKEN
SQ_HOST=https://sonarqube.stuq.ceshiren.com
SQ_TOKEN=a22c79622b232ffb002053a06b5cb3140da87b41
sonarqube 基本环境配置完成,接下来我们就可以去用 scanner 分析代码了
sonar-project.properties
sonar-scanner -Dsonar.projectKey=myproject -Dsonar.sources=src1
配置 | 描述 | 默认值 |
---|---|---|
sonar.host.url | the server URL | http://localhost:9000 |
sonar.projectKey | 项目唯一标记 |
For Maven projects, this defaults to
<groupId>:<artifactId>
|
sonar.projectName | 项目显示名字 |
<name>
for Maven projects
|
sonar.login | 权限认证 token | |
sonar.ws.timeout | 最大超时时间 | 60 |
sonar.sources | 逗号分割的源代码目录 | 默认当前目录,或者从构建工具中读取 |
sonar.newCode.referenceBranch | 是否是新代码的判断基准分支 | 按照当前变更 |
git clone https://github.com/SonarSource/sonar-scanning-examples.git
cd sonar-scanning-examples
cd sonarqube-scanner
sonar-scanner -Dsonar.host.url=$SQ_HOST -Dsonar.login=$SQ_TOKEN
#docker方式
docker run --rm -e SONAR_HOST_URL=$SQ_HOST -e SONAR_LOGIN=$SQ_TOKEN -v /tmp/demo:/usr/src sonarsource/sonar-scanner-cli
<settings>
<pluginGroups>
<pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
</pluginGroups>
<profiles>
<profile>
<id>sonar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!-- Optional URL to server. Default value is http://localhost:9000 -->
<sonar.host.url>
http://myserver:9000
</sonar.host.url>
</properties>
</profile>
</profiles>
</settings>
mvn sonar:sonar
mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.7.0.1746:sonar
mvn org.sonarsource.scanner.maven:sonar-maven-plugin:LATEST:sonar
#编译后直接分析
mvn clean compile sonar:sonar
#执行单元测试后分析,用于收集单元测试与单测覆盖率
mvn clean test sonar:sonar
#执行集成测试后分析,用于收集集成测试与集成测试覆盖率
mvn clean verify sonar:sonar
cd maven-basic
mvn clean verify \
org.sonarsource.scanner.maven:sonar-maven-plugin:LATEST:sonar \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
cd maven-multimodule
mvn clean verify \
org.sonarsource.scanner.maven:sonar-maven-plugin:LATEST:sonar \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
cd maven-multilingual
mvn clean verify \
org.sonarsource.scanner.maven:sonar-maven-plugin:LATEST:sonar \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
mvn clean compile \
org.sonarsource.scanner.maven:sonar-maven-plugin:LATEST:sonar \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
mvn clean verify \
org.sonarsource.scanner.maven:sonar-maven-plugin:LATEST:sonar \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
maven 的基本分析命令已经熟悉了,快去在自己的项目中练习下吧。后面的章节我们会进一步教大家收集测试用例与覆盖率数据
// build.gradle
plugins {
id "org.sonarqube" version "3.3"
}
sonarqube {
properties {
property "sonar.host.url", "https://sonarqube.hogwarts.ceshiren.com"
property "sonar.login", "a22c79622b232ffb002053a06b5cb3140da87b41"
property "sonar.sourceEncoding", "UTF-8"
}
}
# gradle.properties
systemProp.sonar.host.url=http://localhost:9000
#多任务执行
./gradlew clean assemble sonarqube \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
#也可单独执行
gradle sonarqube \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
cd gradle-basic
./gradlew clean assemble sonarqube \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
cd gradle-multimodule-coverage
./gradlew clean build \
codeCoverageReport \
sonarqube -Dsonar.host.url=$SQ_HOST -Dsonar.login=$SQ_TOKEN
Appium UiAutomator/UiObject2-based server for Android UI automation. This module is used by appium-uiautomator2-driver component
./gradlew clean \
assembleServerDebug \
assembleServerDebugAndroidTest \
sonarqube \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
恭喜你获得了 gradle 项目的代码分析能力,快在自己公司的项目中用起来吧。一般移动端项目使用 gradle 是比较多的。
mvn \
clean \
compile \
test \
org.sonarsource.scanner.maven:sonar-maven-plugin:LATEST:sonar \
-Dsonar.host.url=$SQ_HOST \
-Dsonar.login=$SQ_TOKEN
霍格沃兹测试开发
ceshiren.com
JaCoCo is a free code coverage library for Java, which has been created by the EclEmma team based on the lessons learned from using and integration existing libraries for many years.
JaCoCo 是一个免费的 Java 代码覆盖库,它是由 EclEmma 团队根据多年来使用和集成现有库的经验教训创建的。
There are several open source coverage technologies for Java available. While implementing the Eclipse plug-in EclEmma the observation was that none of them are really designed for integration . Most of them are specifically fit to a particular tool (Ant tasks, command line, IDE plug-in) and do not offer a documented API that allows embedding in different contexts. Two of the best and widely used available open source tools are EMMA and Cobertura. Both tools are not actively maintained by the original authors any more and do not support the current Java versions. Due to the lack of regression tests maintenance and feature additions is difficult.
有几种可用的 Java 开源覆盖技术。在实现 Eclipse 插件 EclEmma 时,观察到它们都不是真正为集成而设计的。它们中的大多数专门适用于特定工具(Ant 任务、命令行、IDE 插件),并且不提供允许嵌入不同上下文的文档化 API。两个最好的和广泛使用的开源工具是 EMMA 和 Cobertura。这两个工具都不再由原作者积极维护,也不支持当前的 Java 版本。由于缺乏回归测试,维护和功能添加很困难。
public static void example() {
a();
if (cond()) {
b();
} else {
c();
}
d();
}
public static example()V
INVOKESTATIC a()V
INVOKESTATIC cond()Z
IFEQ L1
INVOKESTATIC b()V
GOTO L2
L1: INVOKESTATIC c()V
L2: INVOKESTATIC d()V
RETURN
export JACOCO_HOME=/Users/seveniruby/ke/shift_left/jacoco-0.8.9
文件 | 用途 |
---|---|
lib/jacocoagent.jar | on the fly 运行时插桩用到的 java agent |
lib/jacococli.jar | jacoco 的命令行工具 |
霍格沃兹测试开发
ceshiren.com
#请使用Java 17运行
git clone https://github.com/spring-io/start.spring.io.git
cd start.spring.io
#设置一个变量,保存起来方便后续引用
export START_SPRING_IO_HOME=$PWD
./mvnw clean package -Dmaven.gitcommitid.skip=true -Dmaven.test.skip=true
#启动被测应用
java -jar $START_SPRING_IO_HOME/start-site/target/start-site-exec.jar
#vim ~/.bash_profile
#修改为你的本地对应地址,每个人可能不同
export JACOCO_HOME=$HOME/ke/shift_left/jacoco-0.8.9
export START_SPRING_IO_HOME=$HOME/ke/shift_left/start.spring.io
```bash {: style="white-space: pre-wrap"} java -javaagent:$JACOCO_HOME/lib/jacocoagent.jar=output=tcpserver -jar $START_SPRING_IO_HOME/start-site/target/start-site-exec.jar
---
### on-the-fly 运行时插桩的多种模式
```bash
#本地文件模式
java -javaagent:$JACOCO_HOME/lib/jacocoagent.jar \
-jar $START_SPRING_IO_HOME/start-site/target/start-site-exec.jar
#tcpserver模式 默认6300端口
java -javaagent:$JACOCO_HOME/lib/jacocoagent.jar=output=tcpserver \
-jar $START_SPRING_IO_HOME/start-site/target/start-site-exec.jar
#删除之前的exec文件,不然会自动合并以前的结果
rm jacoco_tcpserver.exec
java -jar $JACOCO_HOME/lib/jacococli.jar \
dump \
--address 127.0.0.1 --port 6300 \
--reset \
--destfile jacoco_tcpserver.exec
#生成无源代码的报告
java -jar $JACOCO_HOME/lib/jacococli.jar \
report jacoco_tcpserver.exec \
--html jacoco_html \
--classfiles $START_SPRING_IO_HOME/start-site/target/classes/
java -jar $JACOCO_HOME/lib/jacococli.jar \
report jacoco_tcpserver.exec \
--html jacoco_html \
--classfiles $START_SPRING_IO_HOME/start-site/target/start-site-exec.jar \
--classfiles $START_SPRING_IO_HOME/start-site/target/start-site.jar
Caused by: java.lang.IllegalStateException: Can't add different class with same name: org/apache/logging/log4j/util/StackLocator
java -jar $JACOCO_HOME/lib/jacococli.jar \
report jacoco_tcpserver.exec \
--html jacoco_html \
--classfiles $START_SPRING_IO_HOME/start-site/target/classes/ \
--sourcefiles $START_SPRING_IO_HOME/start-site/src/main/java/
掌握了如何获取集成测试覆盖率后,我们就可以根据覆盖率数据与未覆盖代码,来针对性的改进我们的手工测试与自动化测试了。
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.9-SNAPSHOT</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-prepare-agent-integration</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
</execution>
<execution>
<id>default-report</id>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>default-report-integration</id>
<goals>
<goal>report-integration</goal>
</goals>
</execution>
<execution>
<id>default-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>COMPLEXITY</counter>
<value>COVEREDRATIO</value>
<minimum>0.60</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
mvn clean test jacoco:report
mvn clean jacoco:prepare-agent test jacoco:report
#不依赖jacoco插件安装
mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent test org.jacoco:jacoco-maven-plugin:report
open target/site/jacoco/index.html
git clone https://github.com/SonarSource/sonar-scanning-examples.git
cd sonar-scanning-examples/sonarqube-scanner-maven/maven-basic
mvn clean test jacoco:report
cd $START_SPRING_IO_HOME/start-site
mvn clean jacoco:prepare-agent test jacoco:report -Dmaven.gitcommitid.skip=true -Dtest=JavaVersionProjectDescriptionCustomizerTests#java8IsMandatoryMaven
open target/site/jacoco/index.html
#不依赖jacoco插件安装
mvn clean org.jacoco:jacoco-maven-plugin:LATEST:prepare-agent test org.jacoco:jacoco-maven-plugin:LATEST:report -Dmaven.gitcommitid.skip=true -Dtest=JavaVersionProjectDescriptionCustomizerTests#java8IsMandatoryMaven
open target/site/jacoco/index.html
plugins {
id "jacoco"
}
jacocoTestReport {
reports {
xml.enabled true
}
}
git clone https://github.com/SonarSource/sonar-scanning-examples.git
cd sonar-scanning-examples/sonarqube-scanner-gradle/gradle-basic
./gradlew clean build jacocoTestReport
open build/reports/jacoco/test/html/index.html
通过 maven 与 gradle 的项目配置例子,相信大家已经知道了如何在正式的项目中应用了,快用起来吧