原文地址:Jenkins 2.x-实践指南
静态代码分析
静态代码分析是指在不运行程序的情况下,对源代码进行分析或检查,范围包括代码风格,可能出现的空指针,代码块大小,重复的代码等。
没有通过编译,静态代码分析就没有意义。所以在整个 pipeline 中,静态代码分析通常被安排在编译阶段之后。非编译型语言就另当别论了。
代码规范检查
写代码时大括号该不该换行?对于这样的问题,很容易在团队里引发 ”战争“。在笔者看来,像该不该换行这类代码风格的优缺点问题,不是关键问题。关键问题是在于整个团队甚至整个公司所有人是否采用同一套规范。
2017 年阿里巴巴发布了《阿里巴巴 Java 开发手册》(https://github.com/alibaba/p3c
),在行业内引起来不小的轰动。《阿里巴巴 Java 开发手册》(下文简称 p3c)内容包括:命名风格,常量定义等。有了阿里巴巴的 “光环”,公司内所有人就 “代码规范” 达成共识,变得更容易了。至于 p3c 里的规范是否真的是最好的,这就是相对次要的一个问题。
但是新的问题来了,如何实施?安排一个人定期 review 团队成员的代码是否符合代码规范?这样做明显不够 “DevOps”。
解决这个问题的正确思路是让集群来对规范的实施进行检查。我们的构建工具,专业的代码分析器不就是干这件事的吗?
所以,代码规范检查的方法是使用构建工具或代码分析器进行代码规范检查,如果不通过,pipeline 就中止。
使用 PMD 进行代码规范检查
PMD(https://pmd.github.io
)是一款可扩展的静态代码分析器,它不仅可以对代码风格进行检查,还可以检查设计,多线程,性能等方面的问题。
Maven 的 PMD 插件(https://pmd.github.io
)使我们能在 Maven 上使用 PMD。
使用步骤如下:
在 Maven 项目的 pom.xml 中加入 PMD 插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.8</version>
<configuration>
<rulesets>
<ruleset>rulesets/java/ali-comment.xml</ruleset>
<ruleset>rulesets/java/ali-concurrent.xml</ruleset>
<ruleset>rulesets/java/ali-constant.xml</ruleset>
<ruleset>rulesets/java/ali-exception.xml</ruleset>
<ruleset>rulesets/java/ali-flowcontrol.xml</ruleset>
<ruleset>rulesets/java/ali-naming.xml</ruleset>
<ruleset>rulesets/java/ali-oop.xml</ruleset>
<ruleset>rulesets/java/ali-orm.xml</ruleset>
<ruleset>rulesets/java/ali-other.xml</ruleset>
<ruleset>rulesets/java/ali-set.xml</ruleset>
</rulesets>
<printFailingErrors>true</printFailingErrors>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
<goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.alibaba.p3c</groupId>
<artifactId>p3c-pmd</artifactId>
<version>1.3.5</version>
</dependency>
</dependencies>
</plugin>maven-omd-plugin 插件并不会自动使用 p3c-pmd,需要在引入 dependencies 部分手动加入 p3c-pmd 依赖,然后在 rulessets 属性中引入 p3c 的规则。
安装 Jenkins PMD 插件(
https://pmd.github.io/
)。Jenkins PMD 插件的作用是将 PMD 报告呈现在任务详情页中。在 Jenkinsfile 中加入 pmd 步骤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18pipeline {
agent any
tools {
maven 'mvn-3.6.3'
}
stages {
stage('pmd') {
steps {
sh "mvn pmd:pmd"
}
}
}
post {
always {
pmd(canRunOnFailed: true, pattern: '**/target/pmd.xml')
}
}
}执行完成后,可以在任务详情页看到 PMD 报告的连接。单击链接进入报告页面,可以看到更详细的信息。
各静态代码分析器之间的区别
目前每种语言基本上都有自己的静态代码分析器,比如 Java语言,除 PMD外,还有 Check-style,FindBugs 等。但是没有一款能 “大统一”,实现对所有语言,所有场景的支持。
另外,同一种语言下的不同分析器,他们在功能上既有区别,又有重叠,我们需要根据自己的团队情况进行选择。但是不管选择哪款分析器,所有进行静态分析的地方都必须统一分析规则。比如我们决定使用阿里巴巴的开发规范,那么 Maven 插件,IDE 插件以及后面说到的 SonarQube 都必须使用;否则,分析结果可能会不一致,进而影响分析结果的可信度。
单元测试
每种编程语言都有自己的单元测试框架。执行单元测试的工作一般由构建工具来完成。Jenkins做的只不过是执行这些构建工具的单元测试命令。然后对测试报告进行收集,并呈现。
Jenkins 并不会自动帮我们写单元测试,写单元测试还是要靠人。为什么这样说呢?因为笔者发现,不少人认为 Jenkins 的自动化测试是指 Jenkins 代替人自动写测试。
Junit 单元测试报告
Junit 是一个 Java 语言的单元测试框架,由 Kent Beck 和 Erich Gamma 创建。当执行 maven test 命令时,Maven 会执行测试阶段(包括单元测试),然后生成测试报告。
收集并展示 JUnit 测试报告的步骤如下:
安装 Jenkins JUnit 插件(
https://plugins.jenkins.io/junit
).在 Jenkins 中加入 junit 步骤。通常将 junit 步骤放在 post always 中,因为当测试不通过时,我们依然可以收集到测试报告。写法如下:
1
2
3
4
5post {
always {
junit testResults: "**/target/surefire-reports/*.xml"
}
}当 pipeline 运行结束后,在构建页的左边菜单栏及右边详情下都会多出一个连接:Test Result。单击
Test Result
进入,可以看到测试报告的详细信息。Junit 步骤的 testResults 参数支持 Ant 风格路径表达式。
**/target/surefire-reports/*.xml
表示只要是 target/surefire-reports 目录下的 XML 文件就会被当做 JUnit 测试报告处理,而不论 target 在哪个层级的目录下。
JaCoCo 实现代码覆盖率
JUnit 只是方便我们写单元测试的一个框架,但是并没有告诉我们有多少代码被测试覆盖到了。而 JaCoCo 填补了这一空白。JaCoCo 是一个免费的 Java 代码覆盖率的库,能够帮助我们检测出代码覆盖率,并输出覆盖率报告。
JaCoCo 提供了以下几个维度的覆盖率分析:
- 指令覆盖率(Instruction Coverage)
- 分支覆盖率(Branch Coverage)
- 圈复杂度覆盖率(Cyclomatic Complexity Coverage)
- 行覆盖率(Line Coverage)
- 方法覆盖率(Method Coverage)
- 类覆盖率(Class Coverage)
以下是 JaCoCo 插件的使用步骤
安装 JaCoCo 插件(
https://plugins.jenkins.io/jacoco
).在 Maven 项目中引入 JaCoCo 插件,执行 maven jacoco 生成代码覆盖率报告;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report<goal>
</goals>
</execution>
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- jacoco 执行数据的文件路径 -->
<dataFile>target/jacoco.exec</dataFile>
<!-- 输出报告的路径 -->
<outputDirectory>target/jacoco-ut</outputDirectory>
<configuration>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
</systemPropertyVariables>
</configuration>
</plug>使用 jacoco 步骤。jacoco 步骤在 mvn 命令之后执行,写法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37steps {
sh "mvn clean install"
jacoco(
// 代码覆盖率统计文件位置,Ant 风格路径表达式
execPattern: 'target/**/*.exec',
// classes 文件位置,Ant 风格路径表达式
classPattern: 'target/classes',
//源码文件位置,Ant 风格路径表达式
sourcePattern: 'src/main/java',
//排除分析的位置,Ant 风格路径表达式
exclusionPattern: 'src/test*',
//是否禁用每行覆盖率的源文件显示
skipCopyOfSrcFiles: false,
// 如果为 true,则对各维度的覆盖率进行比较。如果任何一个维度的当前覆盖率小于最小覆盖率阈值,则构建状态为失败;
// 如果当前覆盖率在最大阈值和最小阈值之间,则当前构建状态为不稳定;
// 如果当前覆盖率大于最大阈值,则构建成功;
changeBuildStatus: true,
// 字节码指令覆盖率
minimumInstructionCoverage: '30', maximumInstructionCoverage: '70',
// 行覆盖率
minimumLineCoverage: '30', maximumLineCoverage: '70',
// 圈复杂度覆盖率
minimumComplexityCoverage: '30', maximumComplexityCoverage: '70',
// 方法覆盖率
minimumMethodCoverage: '30', maximumMethodCoverage: '70',
// 类覆盖率
minimumClassCoverage: '30', maximumClassCoverage: '70',
// 分支覆盖率
minimumBranchCoverage: '30', maximumBranchCoverage: '70',
// 如果为 true, 则只有所有维度的覆盖率变化量的绝对值小于相应的变化量阈值时,构建结果才为成功
buildOverBuild: true,
// 以下是各个维度覆盖率的变化量阈值
deltaInstructionCoverage: '80', deltaLineCoverage: '80',
deltaMethodCoverage: '80', deltaClassCoverage: '80',
deltaComplexityCoverage: '80', deltaBranchCoverage: '80'
)
}pipeline 运行完成后,我们可以在任务详情页的下方看到报告;
buildOverBuild 和 changeBuildStatus 参数都能影响 Jenkins 任务的结果状态,那么当这两个参数的值都为 true 时,结果是什么呢?由其共同决定。以下是它们的判断逻辑。1
2
3
4
5* Success AND Success = Success
* Unstable AND Unstable = Unstable
* Failure AND Failure = Failure
* X AND Failure = Failure, Failure AND X = Failure, X = Success/Unstable/Failure
* Y AND Unstable = Unstable, Unstable AND Y = Unstable, Y = Success/Unstable
最后,各个维度的覆盖率应该设置多少呢?没有标准的答案。
笔者的经验是先要确定项目是遗留的还是新建的。遗留的旧以当前覆盖率为基线,新建的则设置相对高一些的要求。再看项目的紧急程度,如果非常紧急的话,则可以考虑放低要求。最后看项目的重要程度。如果整个项目在整个架构中起着非常重要的作用,那么覆盖率要求会高一些。
代码覆盖率越高,软件的质量就越高吗?
代码覆盖率越高,软件的质量就越高吗?我们来看看《软件之道:软件开发争议问题剖析》中是怎么说的:
为了找到两者之间的关系,把 Windows Visa(4000 多万行代码,几千个二进制文件,数千名工程师)的分支覆盖率(Branch Coverage)与块覆盖率(Block Coverage) 和其发布六个月内的现场缺陷对应起来。我们观察到覆盖率和质量之间有弱正相关性,预测查准率和查全率较差(查准率为 83.8%,查全率为 54.8%)
书中最终给出的答案是:
代码覆盖率最好不要单独使用,而是需要与其它指标,如代码变动率,复杂度等一并考虑。
所以,如果考虑将代码覆盖率作为团队开发人员的 KPI,请慎重。
性能测试
Taurus 是一个开源的自动化框架用于运行各种开源负载测试工具和功能测试工具。其支持最流行的开源负载测试工具 Apache JMeter,Selenium,Gatling, The Grinder 等。Taurus 的关键特性有:
- 我们可以使用 YAML 或 JSON 来描述性能测试。这也正是我们想要的 test as code。
- 它会根据我们选择的性能测试类型自动下载相应的工具。比如在下例中会使用 JMeter,那么 Taurus 会自动下载 JMeter并安装。
Jenkins 的 Performance 插件就是使用 Taurus 来进行性能测试的。在进行性能测试之前,首先需要准备环境。
准备性能测试环境
- 在运行性能测试环境的机器上,准备 Python 环境。
- 安装 Performance 插件(
https://plugins.jenkins.io/performance
). - 安装 Taurus?不需要自行安装,Performance 插件如果发现机器上没有安装 Taurus,它会自动运行 pip install bzt 命令进行安装。
运行 JMeter 测试
假设你平时都是手动执行 JMeter 测试的,现在希望将它自动化。着很简单,只需要两步。
在现有的项目中加入 Jenkinsfile。
1
2
3
4
5
6
7
8
9
10pipeline {
agent any
stages {
stage('performance test') {
steps {
bzt params: 'blaze_exist_jmeter_config.yml'
}
}
}
}在项目中加入 blaz_exist_jmeter_config.yml 文件
1
2
3
4
5
6
7
8
9
10
11
12execution:
- scenario: simple
ecenarios:
simple:
script: SimpleTestPlan.jmx
modules:
jmeter:
# 注意,下载文件必须使用 .zip 后缀
download-link: https://mirrors.tuna.tsinghua.edu.cn/apache/jmeter/binaries/apache-jmeter-{version}.zip
version: 5.2
blaz_exist_jmeter_config.yml 是 Taurus 的配置文件,用于描述如何进行性能测试。以上配置很简单,就是执行一个名为 simple 的场景(scenario),这个场景就是执行现有的 JMeter 脚本。modules 配置了 JMeter 的下载地址以及版本。上例中我们指定了国内的下载链接,避免从国外下载。
在 Jenkinsfile 中,bzt 是 Performance 插件提供的一个步骤。其参数如下:
- params:字符串类型,Taurus 配置文件的路径。
- alwaysUseVirtualenv: 布尔类型,如果为 false,则不使用 virtualenv 进行环境隔离。默认值为 true。
- bztVersion: 字符串类型,bzt 的版本
- generatePerformanceTrend:布尔类型,是否在 Jenkins 项目详情页生成性能趋势图。默认值为 true。
- useBztExitCode: 布尔类型,是否使用 bzt 步骤的退出码作为 Jenkins 项目的构建结果。默认值为 true。
- useSystemSitePackages: 布尔类型,是否为 virtualenv 加上 ”–system-site-packages“ 参数。默认值为 true。
- workingDirectory: 字符串类型,指定 bzt 的工作目录。
- workspace: 字符串类型,已经废弃们请使用 workingDirectory。
只有 params 参数是必须的,其他参数都是可选。
至此,以上用法可以满足大部分人在 Jenkins 上使用 JMeter 的需求。关于 Taurus 配置文件的更多语法,可以前往 Taurus 官网学习。