NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

有赞 GO 项目单测、集成、增量覆盖率统计与分析

  • 2020-03-31
  • 本文字数:4968 字

    阅读完需:约 16 分钟

有赞 GO 项目单测、集成、增量覆盖率统计与分析

一、引言

我是一名中间件 QA,我对应的研发团队是有赞 PaaS,目前我们团队有很多产品是使用 go 语言开发,因此我对 go 语言项目的单测覆盖率、集成以及增量测试覆盖率统计与分析做了探索。

二、单测覆盖率以及静态代码分析

2.1 单测覆盖率分析

Go 语言自身提供了单元测试工具 go test,单元测试文件必须以 *_test.go 形式存在, go test 工具同时也提供了分析单测覆盖率的功能。因为需要将单测覆盖率上传到 sonar 平台展示,所以必须将覆盖率文件转换成能被 sonar 识别的格式,因此,还需要另外一个命令行工具 gocov。首先我们使用 go test 生成覆盖率输出文件 cover.out,并通过 gocov 工具来将生成的覆盖率文件 cover.out 转换成可以被 sonar 识别的 Cobertura 格式的 xml 文件。如下所示:


go test -v ./... -coverprofile=cover.out #生成覆盖率输出gocov convert cover.out | gocov-xml > coverage.xml #将覆盖率输出转换成xml格式的报告
复制代码


将生成的单测覆盖率报告发送到 sonar 平台上来展示。

2.2 静态代码分析

Go 静态代码分析工具有两个,分别是 gometalinter 和 golangci-lint,我们现在使用的是 golangci-lint,因为 gometalinter 已经停止维护,而且作者也推荐去使用 golangci-lint

2.2.1 golangci-lint 的安装

以下是安装 golangci-lint 推荐的两种方法:


  • 将二进制文件安装在 (go env GOPATH)/bin/golangci-lint 目录下 curl-sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin vX.Y.Z

  • 或者将二进制文件安装在 ./bin/ 目录下 curl-sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s vX.Y.Z


安装完成之后可以通过使用 golangci-lint--version来查看它的版本。

2.2.2 golangci-lint 的使用

在需要进行静态代码扫描的目录下执行 golangci-lint run,此命令和 golangci-lint run./… 命令等效,表示扫描整个项目文件代码,并进行监测,也可以通过指定 go 文件或者文件目录名来对特定的代码文件或者目录进行代码扫描,例如 golangci-lint run dir1 dir2/...dir3/file1.go


ps:扫描指定目录的时候是不支持递归扫描的,如果要进行递归扫描需要在目录路径后面追加 /…


默认情况下 golangci-lint 只启用以下的 linters:


Enabled by default linters:


deadcode: 发现没有使用的代码


  • errcheck: 用于检查 go 程序中有 error 返回的函数,却没有做判断检查

  • gosimple: 检测代码是否可以简化

  • govet (vet, vetshadow): 检查 go 源代码并报告可疑结构,例如 Printf 调用,其参数与格式字符串不一致

  • ineffassign: 检测是否有未使用的代码、变量、常量、类型、结构体、函数、函数参数等

  • staticcheck: 提供了巨多的静态检查,检查 bug,分析性能等

  • structcheck:发现未使用的结构体字段

  • typecheck: 对 go 代码进行解析和类型检查

  • unused: 检查未使用的常量,变量,函数和类型

  • varcheck: 查找未使用的全局变量和常量


Disabled by default linters:


bodyclose: 对 HTTP 响应是否 close 成功检测


  • dupl: 代码克隆监测工具

  • gochecknoglobals: 检查 go 代码中是否存在全局变量

  • goimports: 做所有 gofmt 做的事. 此外还检查未使用的导入

  • golint: 打印出 go 代码的格式错误

  • gofmt: 检测代码是否都已经格式化, 默认情况下使用 -s 来检查代码是否简化

  • ……………………………


未启用的还有很多工具,可以通过使用 golangci-lint help linters命令查看还有哪些工具可以使用,如果想要启用没有默认开启的工具,可以在执行命令时使用 -E 参数来启用,比如要启用 golint 的话,只需要执行一下命令 golangci-lint run-E=golint。除了用 -E 来启动参数外,还可以指定最长执行时间 —deadline、跳过要扫描的目录 --skip-dirs 等等。如果要了解更多,请使用 golangci-lint run-h来查看。


特别注意 —-exclude-use-default 参数,golangci-lint 对于上面默认的启用 linters 中做了一些过滤措施,比如对于 errcheck ,它不会扫描 ((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv) 这些函数返回的 error 是否被 checked,所以如果代码中使用到这些函数,并且没有接收 error 的话是不会被扫描到的。类似的还有golintgovetstaticcheckgosec需要注意。如果想要不过滤这些就需要使用 --exclude-use-default=false来启用。

2.3 接入 sonar

go 接入 sonar 需要 sonar-scanner 工具以及 sonar-project.properties 文件。

2.3.1 sonar-scanner

sonar-scanner 是 sonar 官方提供的代码扫描器,下载地址是 https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner。下载好之后解压,解压后的目录下有四个文件夹,分别是 bin、conf、jre、lib,然后将 bin 文件夹路径添加到 $PATH 环境变量下,使用 sonar-scanner-v 来查看版本。

2.3.2 sonar-project.properties

sonar-project.properties 文件的作用主要是配置 sonar 扫描器扫描哪些类型的文件以及文件目录,最后将报表结果上报到 sonar 服务器,sonar-project.propertie 内容如下:


内容如下:


#sonar安装的服务器地址sonar.host.url=http://ip:port#服务器账号sonar.login=root#服务器密码sonar.password=root#项目使用的语言sonar.language=go#项目的独特关键字,maven 项目是 <groupId>:<artiactId>,go 项目自己定义就可以sonar.projectKey=projectKey#将在web界面上显示的名字sonar.projectName=demo#项目版本sonar.projectVersion=1.0#需要分析的源码目录的路径sonar.sources=.sonar.exclusions=**/*_test.go,**/vendor/**sonar.tests=.sonar.test.inclusions=**/*_test.gosonar.test.exclusions=**/vendor/**#golangci-lint 报告路径sonar.go.golangci-lint.reportPaths=report.xml#单测覆盖率报告地址sonar.go.coverage.reportPaths=cover.out
复制代码


在项目目录下分别执行go test-v./...-coverprofile=cover.out以及 golangci-lint run--out-format checkstyle./...&gt;report.xml等生产报告,并执行 sonar-scan 来将生成的报告上传到服务器。这里默认在使用的是 sonar8.1 已经支持了 golangci-lint报告主页


三、集成测试覆盖率分析

对于 Go 项目没有类似 java jacoco 这样的第三方测试工具,就算是开源的第三方工具,一般单元测试执行以及单测覆盖率分析都是使用 Go 自带的测试工具 go test 来执行的。


阅读了 GO 的官方博客之后发现其实针对二进制文件是有类似的工具 gcov。在文章中作者也说了,对于在 go 1.2 之前,其实也是使用类似 gcov 的方式对二进制程序在分支上设置断点,在每个分支执行时,将断点清除并将分支的目标语句标记为 “covered” 。


但是通过文章可以知道,在 go 1.2 之后是不支持使用此种方式,而且也不推荐使用 gcov 来统计覆盖率,因为执行二进制分析是很有挑战且很困难的,它还需要一种可靠的方式来执行跟踪绑定到源代码,这也很困难,这些问题包括不准确的调试信息和类似内联函数使分析复杂化,最重要的是,这种方法非常不便携。

3.1 解决方法

通过查找资料,发现了一个并不完美但是可以解决这个问题的方法。go test 中有一个 -c 的 flag,可以将单测的代码和被单测调用的代码编译成二进制包执行,但是这种方式并没有将整个项目的代码包含进去,不过可以通过增加一个测试文件 main_test.go,文件内容如下:


func TestMainStart(t *testing.T) {    var args []string    for _, arg := range os.Args {        if !strings.HasPrefix(arg, "-test") {            args = append(args, arg)        }    }    os.Args = args    main()}
复制代码


将主函数放在此测试代码中,由于 Go 的入口函数是 main 函数,所以这样就会将整个 Go 项目都打包成一个已经插桩的二进制文件,如果项目启动的时候需要传入参数,则会将其中程序启动时传入的不是 -test 标记的参数放入到 os.Args 中传递给 main 函数。以上代码也可以自己在测试文件中增加消息通知监听,来退出测试函数。当集成测试跑完后就可以得到覆盖率代码,整个流程可参考下图:



#第一步:执行集成测试,并将此函数编译成二进制文件go test -coverpkg="./..." -c -o cover.test#第二步:运行二进制文件,指定运行的测试方法是 TestMainStart,并将覆盖率报告输出./cover.test -test.run "TestMainStart" -test.coverprofile=cover.out#第三步:将输出的覆盖率报告转换成 html 文件(html 文件查看效果比较好)go tool cover -html cover.out -o cover.html#第四步:生成 Cobertura 格式的 xml 文件gocov convert cover.out | gocov-xml > cover.xml
复制代码

3.2 缺点

  1. 必须所有 Go 语言项目中新增一个这样的测试代码文件,才可以使用

  2. 必须退出进程才可以获得报告,但是如果测试程序是在 k8s 的 pod 中,一旦程序退出,pod 就会自动退出无法获取到文件

  3. 想要得到测试覆盖率数据不能像 jacoco 那样直接调用接口可以 dump 到本地,程序必须增加一个接收信号量的参数,保证主函数的退出,不然集成测试代码跑完,覆盖率信息是不会写到磁盘的

  4. 由于上面的原因,报告储存在远端,无法下载到当前 Jenkins 上,要去远端 dump 文件下来分析

  5. 不能将分布式的应用的数据结合起来之后做全量统计(只能跑单个应用)


以上缺陷在有赞 paas 团队通过一些不是特别优雅的方式解决,以下是解决方案

3.3 优化

ps:由于当前有赞 PaaS 的 ci 环境是在 k8s 集群中实现的,所以这里就针对 k8s 中 的优化方案

3.3.1 针对编译前需要新增一个测试文件,包裹 main 函数

测试函数也是要求所有项目中增加一个测试文件,或者 Jenkins 编译部署镜像之前在 pipline 中生成一个文件

3.3.2 针对以上必须程序退出才可以或许到测试覆盖率报告的缺点:

假设 k8s 基础镜像中已经装好 python,我在启动 pod 的时候默认启动两个服务,一个是被测试的服务,一个是 python 启动的 http 服务。


然后将项目服务的启动写入脚本中,并在 deployment 中通过 nohup 启动服务,并再启动一个 python 服务


    spec:      containers:      - command:        - /bin/bash        - -c        - (nohup /data/project/start.sh &);(cd python && -m SimpleHTTPServer 12345)        image: $imageAddress
复制代码


杀死项目服务后,因为还有 python 服务在,pod 不会退出,可以拿到覆盖率测试报告

3.3.3 覆盖率报告在远端,如何在跑完 Jenkins 任务后来直接获取到报告:

可以在跑集成测试后通过执行 http 请求来获取容器内的 cover.out,比如 wget http://{ip}:{port}/{path}/cover.out,并将此覆盖率报告编译成 Cobertura 格式的 xml,放入到 Jenkins 中统计。


如果是执行了多个服务端,需要合并覆盖率报告,可以使用 gocovmerge

3.3.4 如何在 k8s 中自动化 kill 程序让其退出:

对于退出程序可以直接在集成测试代码中使用 kubectl 命令将 pod 中的程序 kill


pid=`kubectl exec $podname -c $container -n dts -- ps -ef | grep $process | grep -v grep | awk '{print $2}'`kubectl exec $podname -c $container -n $namespace -- kill $pid
复制代码

3.4 jenkins 报告

四、集成测试增量覆盖率分析

4.1 diff_cover

增量覆盖率分析我们选择了开源工具 diffover,diffcover 是用 python 开发,通过 git diff 来对比当前分支和需要比对的分支,主要针对新增代码做覆盖率分析。

4.2 安装

安装 diff_cover 的机器需要有 python 的环境,有两种安装方式:


1、通过 pip 来直接下载安装


pip install diff_cover


2、通过源代码安装


pip install diff_covers

4.3 使用方式

ps:必须在需要对比的项目目录下运行!!!

4.3.1 生成单元测试覆盖率报告

go test-v./...-coverprofile=cover.outgocov convert cover.out|gocov-xml&gt;coverage.xml

4.3.2 增量覆盖率分析

diff-cover coverage.xml--compare-branch=xxxx--html-report report.html


–compare-branch:是选择需要对比的分支号

–html-report:是将增量测试报告生成 html 的报告模式

除了以上参数,此工具还有很多其他参数,比如

–fail-under:覆盖率低于某个值,返回非零状态代码

–diff-range-notation:设置 diff 的范围,就是 git diff{compare-branch}{diff-range-notation} 的作用等等。

具体可以通过 diff_cover-h 来获得更多详细的信息

4.4 报告

  1. 命令行展示



  1. HTML 展示



本文转载自公众号有赞 coder(ID:youzan_coder)。


原文链接


https://mp.weixin.qq.com/s/mNGkMggkkuRSuflHw3vIyA


2020-03-31 10:135179

评论

发布
暂无评论
发现更多内容

学生管理系统

KING

Web入门开发【二】

小院里的霍大侠

编程开发 初学者 入门实战 Web入门

架构实战营 3-5 消息队列备选架构随堂练习

西山薄凉

「架构实战营」

SDK更新不了问题解决

智趣匠

android hosts SDK 教程

数据库原理及MySQL应用 | 约束

TiAmo

主键约束 数据库· 12月月更

SRE运维解密-应对过载

董哥的黑板报

微服务 SRE 限流 SRE实践

运维进阶训练营 -W09H

赤色闪电

运维

Web入门:CSS下拉图片

小院里的霍大侠

JavaScript Web 初学者 入门者

安卓、iOS、小游戏三端同发?介绍给你一个新方式 - 普洱WebGL

僵尸浩

typescript 小游戏 Unity3D

数据可视化图表系列解析——饼图

Data 探险实验室

数据分析 可视化 数据可视化 可视化数据 可视化工具

Web入门开发【四】

小院里的霍大侠

Web 编程开发 初学者 入门实战

【IntelliJ IDEA】连接https报错问题: E230001: Server SSL certificate verification failed:

No8g攻城狮

svn IDEA

OpenTelemetry系列 (四)| 如何使用Java Agent来实现无侵入的调用链

骑牛上青山

Java javaagent 调用链 OpenTelemetry 微服务调用链

架构误区系列10:不合理的分层

agnostic

软件分层

spaa 22

黄敏

2022年浪过的那些城市

SAP虾客

杭州 2022年 无锡 东莞 SAP项目

【web 开发基础】PHP 中的预定义数组详解续集 (48)

迷彩

post GET 文件上传 PHP基础 预定义数组

一文读懂什么是低代码开发?

YonBuilder低代码开发平台

HTTP请求首部字段

穿过生命散发芬芳

HTTP 12月月更

架构实战营 3-4 架构设计后期随堂练习

西山薄凉

「架构实战营」

银行如何快速落地营销数字化?

中关村科金

人工智能 大数据 AI 数字化转型 数字化

启科QuSaaS真随机数解决方案与Amazon Braket结合实践

启科量子开发者官方号

人工智能 量子计算 随机数

优化器核心技术—Join Reorder

KaiwuDB

Flink 在米哈游的应用实践

Apache Flink

大数据 flink 实时计算

基于Go的缓存实现

俞凡

架构

【IntelliJ IDEA】idea 2018版本中没有SVN按钮或者图标的解决方法

No8g攻城狮

svn IDEA git 学习

一文告诉你如何选择低代码供应商?

YonBuilder低代码开发平台

FFA 2022 主会场 Keynote:Flink Towards Streaming Data Warehouse

Apache Flink

大数据 flink 实时计算

【架构设计】你的类足够“专一”吗

JAVA旭阳

Java 架构

2022-12-25:etcd可以完全替代zookeeper,原因是k8s用的etcd,不用担心不成熟。请问etcd部署在k3s中,yaml如何写?

福大大架构师每日一题

云原生 k8s etcd k3s 福大大

架构训练营模块三作业

张建闯

架构实战营

有赞 GO 项目单测、集成、增量覆盖率统计与分析_软件工程_赵海强_InfoQ精选文章