研发一体化平台-质量管理模块,是整个研发一体化平台的核心组成部分之一。该模块主要的作用是在开发过程中检测代码中的错误、漏洞和安全隐患,从可靠性、安全性、可维护性、覆盖率、重复率等方面分析项目代码,确保代码质量和规范性。
软件开发中,向代码仓库提交新的代码的过程中药确保新提交的代码应该在正确性、规范性、安全性、稳定性等多个维度满足系统要求,保障集成了新功能后的系统可正常编译、构建,单元测试用例可以成功执行,确保新代码符合开发规范,新代码在单测与集成测试过程中不能引入新的安全问题。质量管理模块采用一种焦作持续集成的可以即时触发的自动化校验流程,通过在流程中配置准入门禁,使用技术手段对重要指标做量化,做到能够及早发现非功能性问题,提高交付测试的质量,进而保证系统整体稳定性。持续集成通过流水线方式定义集成过程的各个阶段(stages),以及每个阶段内要执行的具体任务(jobs)。在满足触发条件时,比如push代码或新建合并请求时,系统会创建一条工作流水线,用于执行预定义的脚本。流水线是持续集成的顶级组件,可以在流水线中观察任务执行过程、下载产出物以及获取执行结果。
基于流水线的分支策略:
设置保护分支
在智能开发平台上,点击项目-->设置-->仓库-->受保护分支,将分支设置为受保护分支。
设置流水线成功才允许合并:项目-->设置-->合并请求-->合并检查
新建dev分支,在项目根目录下创建.gitlab-ci.yml脚本文件,使用如下内容:
stages:
- build
- test
build-job:
stage: build
script:
- mvn clean package -DskipTests
test-job:
stage: test
script:
- mvn test
脚本中定义了build和test两个阶段,以及具体的流水线任务。
将脚本提交并推送到仓库,此时会触发流水线,在“项目管理-->CI/CD-->流水线”下可以看到流水线状态:
点击流水线编号“#1”可以查看流水线详情
点击具体的任务“build-job”可以查看当前任务执行信息:
所有任务执行完成后,流水线状态为“已通过”:
提交合并请求默认不会触发流水线,可以修改gitlab-ci.yml文件,通过workflow关键字设置流水线触发条件:
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
提交合并请求后会自动触发合并请求流水线:
如果流水线失败,则leader不能执行合并动作:
当流水线通过是,允许项目管理者合并代码:
在合并代码前应进行Code Review,可以直接在合并请求页面的“变更”选项卡中进行Code Review,对有问题的代码启动评审:
提交评审后,对应开发人员会收到提醒邮件,开发人员修复代码问题重新提交后,点击“解决主题”,确保合并请求所有主题已解决
可以从多个维度评价代码质量,从最基本的代码格式、到代码重复率、单点复杂度、再到安全合规性,代码质量管理就是要找出代码中的上述问题,防止“烂代码”带来的破窗效应和安全问题,提高项目代码的可维护性,进而提高开发效率。传统的代码质量管理主要依赖代码规范倡议和人工code review,效率较低、存在主观性、容易遗漏质量问题。代码质量管理平台引入了自动化扫描分析的机制,其原理是使用扫描器分析项目代码,扫描过程中采用一系列预定义规则来识别代码问题,在分析完成后出具整体质量报告,也可以在不同维度设置质量准入标准,从而提早发现质量问题,降低人工code review的工作量。
质量管理平台最重要的部分就是预定义的扫描规则,尤其是针对安全性的分析的AST(Application Security Test)平台更需要一套紧跟最新漏洞列表的规则集合,对规则的维护需要付出很大的成本。质量管理平台可以分析超过25种编程语言,扫描规则可配置,支持插件机制可以对功能和代码规则进行扩展,且易于和流行的CI/CD平台集成。此外,质量管理平台还支持开发自定义插件进行规则扩展,以Java语言为例,目前插件市场已经有针对PMD、CheckStyle、SpotBugs、P3C等常用规则的支持。
选择代码规则Tab页面,可以查看所有代码规则。
在页面左侧,选择编程语言,可以查看相应编程语言的所有规则。还可以通过下方的类型、标签、严重性、状态等筛选标签进行筛选。
点击右侧某一具体规则,可以看到规则的详细信息:
“质量配置”是在进行代码分析时使用的规则集合,每一种编程语言都有内置的默认质量配置,质量配置可以复制,也可以继承,每一个项目要绑定一个质量配置,不指定则使用默认的质量配置。
选择质量配置Tab页面,可以查看所有质量配置列表。
点击某一具体质量配置名称,可以查看该质量配置的所有相关规则分类:
点击左侧相应数字,可以跳转到该质量配置引用的代码规则。
点击右上角 创建 按钮,可以创建新的质量配置。
选择适配的编程语言,填入配置名称,点击创建即可。配置创建完成后,可以通过激活规则来丰富质量配置的规则。
点击激活即可添加代码规则到质量配置。
项目代码分析完成,质量管理平台会从多个维度对本次分析进行评定,生成评定结果,质量阈就是预定义的一个质量标准,如果本次分析的评定结果不符合质量阈标准则扫描不通过,每一个项目都要绑定一个质量阈配置,不指定则使用默认的质量配置。质量阈通过一下几个维度评价代码,确保代码符合CleanCode
方法论:
点击创建按钮,可以创建新的质量阈:
通过指定测试代码覆盖率、代码重复行数、可维护性评级、可靠性等级、安全等级等指标确定项目代码质量标准,质量管理平台在检测代码后,低于该标准的项目将提示检测不通过。
点击项目tab页,可以查看质量管理平台上所有项目的质量分析结果列表和概况。
从该页面中可以看出,质量分析出的问题:bug、漏洞、代码以为、单元测试覆盖率、代码重复率、整体代码行数等。还可以看出,质量阈的检测结果为不通过。点击项目进入项目总览页面:
可以看到本次提交产生的新代码问题。点击全部代码,可以看到该项目历次提交导致所有问题:
点击具体数字,可以跳转至问题列表:
点击问题,可以跳转到导致问题出现的代码位置及问题的详细说明:
开发人员可根据提示进行代码修改。
点击活动,可以看到代码质量趋势图:
SonarLint
插件SonarLint是一个免费的开源IDE扩展,几乎支持所有流行的IDE,像拼写检查器一样,SonarLint会显示缺陷并提供实时反馈和清晰的修复指导,以便从一开始就提交干净的代码,降低GitLab流水线的失败比例,下面以Jetbrains IDEA为例进行说明。
插件下载地址:
https://plugins.jetbrains.com/plugin/7973-sonarlint#JetBrains
也可以在IDEA设置-->Plugins-->Marketplace
SonarLint自带一套代码规则,为了与SonarQube流水线代码规则保持一致,需要使用Connect模式,配置SonarQube地址与访问凭证,IDEA设置-->Tools-->SonarLint-->Settings,点击加号新增服务器连接配置,输入服务器地址:
点击下一步,输入访问凭证,可以使用SonarQube用户令牌或者用户名密码:
点击下一步验证连接,选择是否接收SonarQube通知,最后点击Create完成配置创建
IDEA设置-->Tools-->SonarLint-->Project Settings,将当前项目绑定到SonarQube,可以使用新的ProjectKey,或者选择远程SonarQube上的现有项目
默认情况下,SonarLint会自动触发扫描,也可以在需要扫描的项目路径上右键-->Analyze-->Analyze with SonarLint
当开发的代码存在问题时,SonarLint会高亮提示,鼠标悬停会有Tips提示,同时IDE的工具窗格中可以查看当前的所有问题列表,点击相关条目会给出改进建议:
代码覆盖率(Code Coverage)是用于量化应用程序在测试过程中被执行的代码占全部代码的比例,目的是衡量测试用例的质量,减少测试盲区。在持续集成过程中收集代码覆盖率,作为评价单元测试质量的一个重要标准,是很有必要的。每一种编程语言都有对应的代码覆盖率报告生成工具,针对Java语言,主要的开源工具有Jacoco、Cobertura、Emma和OpenClover,持续活跃的项目只有Jacoco,每年都有新的release版本发布,为Maven、Gradle等构建工具提供插件。
Jacoco使用ASM字节码框架,在原有class字节码中的指定位置插入探针字节码,形成新的字节码指令流。Jacoco的探针实际是一个布尔值,当代码执行到探针位置时,将其置为true,该探针前面的代码会被认为执行过,提取并统计探针信息形成最终的覆盖率报告。Jacoco的工作模式有两种,离线模式(offline)和在线模式(on-the-fly),离线模式是在程序编译时就生成已插桩的字节码文件,在线模式是在JVM启动时指定一个javaagent,在类加载时对字节码进行动态插桩。Jacoco生成覆盖率报告需要两个步骤,首先提取探针信息形成一个或多个exec文件,然后合并exec文件进行统计生成覆盖率报告。在覆盖率报告中,Jacoco提供了多种尺度的覆盖率计数器,包含指令级(Instructions,C0 coverage)、分支(Branches,C1 coverage)、圈复杂度(Cyclomatic Complexity)、行(Lines)、方法(Non-abstract Methods)和类(Classes)覆盖率计数器。
基于Maven构建的Java项目有两种方式使用Jacoco,第一种是在项目的pom文件中,在build域增加Jacoco插件,这种方式可以灵活指定项目中要忽略覆盖率的类,比如一些POJO类,单元测试阶段完成后会自动生成覆盖率报告;第二种是在单元测试阶段使用外挂的Maven插件,这种方式不需要修改项目配置,但是不支持忽略指定类的覆盖率。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.1</version>
<configuration>
<destFile>target/coverage-reports/jacoco-unit.exec</destFile>
<dataFile>target/coverage-reports/jacoco-unit.exec</dataFile>
<includes>
<include>**/service/**</include>
<include>**/controller/**</include>
<include>**/*Tests.java</include>
<include>**/*Test.java</include>
</includes>
<!-- rules指定检查规则 使用jacoco:check检查时如果不通过则构建失败 -->
<rules>
<rule implementation="org.jacoco.maven.RuleConfiguration">
<element>BUNDLE</element>
<limits>
<!-- 方法覆盖到50% -->
<limit implementation="org.jacoco.report.check.Limit">
<counter>METHOD</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
<!-- 分支覆盖到50% -->
<limit implementation="org.jacoco.report.check.Limit">
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
<!-- 类覆盖到100%,不能遗失任何类 -->
<limit implementation="org.jacoco.report.check.Limit">
<counter>CLASS</counter>
<value>MISSEDCOUNT</value>
<maximum>0</maximum>
</limit>
</limits>
</rule>
</rules>
</configuration>
<executions>
<execution>
<id>jacoco-initialize</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
</execution>
<execution>
<id>jacoco-site</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
单元测试完成后在项目的target/site/jacoco目录下可以查看生成的覆盖率报告
GitLab支持在合并请求的变更页面中展示代码行的覆盖状态,需要在流水线中生成Cobertura格式的覆盖率报告,Cobertura项目本身已经停止维护,不支持Java8新增语法,为了展示覆盖状态,可以使用转换工具将Jacoco报告转换为Cobertura格式提供给GitLab,转换工具参考:
https://github.com/Shubzz-02/Jacoco2Cobertura
CI脚本:
test:
stage: test
script:
- mvn --batch-mode org.jacoco:jacoco-maven-plugin:0.8.10:prepare-agent test org.jacoco:jacoco-maven-plugin:0.8.10:report
artifacts:
expire_in: 1 week
when: always
# 收集单元测试报告
reports:
junit: target/surefire-reports/TEST-*.xml
# 归档Jacoco报告
paths:
- target/site/jacoco
coverage:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
stage: test
needs: ["test"]
coverage: "/Total.*?([0-9]{1,3})%/"
script:
# 提取总覆盖率
- grep Total target/site/jacoco/index.html
# 转换为Cobertura格式
- java -jar /usr/local/utils/Jacoco2Cobertura-1.0.3.jar -j target/site/jacoco/jacoco.xml -s ${CI_PROJECT_DIR}/src/main/java/ -d target/cobertura.xml
allow_failure: true
artifacts:
expire_in: 1 week
reports:
# 收集Cobertura格式覆盖率报告
coverage_report:
coverage_format: cobertura
path: target/cobertura.xml
paths:
- target/cobertura.xml
修改.gitlab-ci.yml中test作业的script命令:
mvn --batch-mode org.jacoco:jacoco-maven-plugin:0.8.10:prepare-agent test org.jacoco:jacoco-maven-plugin:0.8.10:report
查看插件支持的参数可以执行:
mvn help:describe -Dplugin=org.jacoco:jacoco-maven-plugin:0.8.10 -Ddetail
合并请求页面展示覆盖率:
合并请求CodeReview页面展示覆盖状态: