iOS 测试 iOS 手工测试代码覆盖率获取

不二家 · 2017年05月02日 · 最后由 xushuchuan 回复于 2020年04月07日 · 4018 次阅读
本帖已被设为精华帖!

背景

事情源于iOS (Object-C) 非单元测试状态下代码覆盖率获取尝鲜,让我恍然大悟,覆盖率可以基于手工测试统计。
很感激这篇文章的详细介绍,依据这篇文章,我手工走通了一遍,然后就开始调研怎么落地在我们项目。

难点

手工走通这篇文章之后,落实在自动化上,有两个难点:

  • 客户端的 *.gcda 文件如何生成,并传递出来?
  • 自己写的基于 Flask 的服务端接收并存储.gcda 之后,怎么同步打包过程中的.gcno 文件?
  • 由于 XcodeCoverage 其实是不支持在 linux 上执行,怎么把服务部署在 linux 上,并保证可以执行?
  • 如何妥善处理客户端发送 *.gcda 文件的并发问题,并且,合并生成的测试覆盖率?

解决方案

我在我们项目中已经通过自动化的方式,实现了 XcodeCoverage 手工覆盖率监测,并开始这个项目的内测。对于接触这篇文章前,最好先通过手工方式走通一遍iOS (Object-C) 非单元测试状态下代码覆盖率获取尝鲜

我的解决方案分三步:

  • 第一步:解决 linux 不能执行 XcodeCoverage 命令问题
  • 第二步:基于 Flask 搭建了一个服务,用于接收.gcda 文件,并请求 iOS 客户端同学实现了,集成 XcodeCoverage 进 Pods 包,然后将.gcda 文件发到我写的服务上;
  • 第三步:优化 Flask 服务,并且将 XcodeCoverage 等命令实现,通过 schedulers 后台运行,并优化了执行逻辑。

下面我一一进行简单阐述。

第一步:在 linux 上运行

部署在 linux 上调用 XcodeCoverage 命令,会报错:

Out of memory!
Reading tracefile Coverage.info
lcov: ERROR: no valid records found in tracefile Coverage.info
Reading tracefile Coverage.info
lcov: ERROR: no valid records found in tracefile Coverage.info
cat: /.xcodecoverageignore: No such file or directory
Reading tracefile Coverage.info
lcov: ERROR: no valid records found in tracefile Coverage.info
Reading data file Coverage.info
genhtml: ERROR: no valid records found in tracefile Coverage.info

直接给出解决方案:

# 修改/envcov.sh为
 LCOV() {
     "${LCOV_PATH}/lcov" "$@"
 }

# 修改/getcov为
"${LCOV_PATH}/genhtml" --ignore-errors source --output-directory . "${LCOV_INFO}"

简单解释就是执行 lcov 生成测试覆盖率.info 文件的时候稍微有点改变,我还是很佩服 XcodeCoverage 这个项目的作者,只是改了一点点就支持 linux 执行。

第二步:接收并处理 *.gcda 文件

其实这里更多的是依赖客户端同学的配合,他帮我实现了,从手机端生成 *.gcda 文件以后,并打了个以当时时间戳为名的 zip 包,然后直接 post 到我写的服务接口上。

客户端在传递 *.zip 包同时,在 header 里面增加了关于这个包的版本号,构件号,userid,和固件号等信息,我在后台解析以后统一存入一个表。

第三步:执行 XcodeCoverage 等一系列命令

其实接收了*.gcda文件只是万里长征第一步,还需要将在 mac slave 上打包过程中的临时性文件,*.gcno文件转存到 linux 上,并且需要修改*.gcno的文件中的路径。这样才能执行 XcodeCoverage 命令。

在这个过程中,我踩过的坑有:

  • 复制 *.gcno 文件

我通过 scp 的方式,先实现 linux 可以免密登录打包的 mac slave,由于我们执行打覆盖率的包是指定 slave 的,然后只需要将 linxu 和 slave 互相信任免密登录,然后再执行完打包之后,通过如下命令:

#!/bin/bash
path="=Pods/XcodeCoverage/env.sh"

while read line
do
name=`echo $line|awk -F '=' '{print $1}'`
value=`echo $line|awk -F '=' '{print $2}'`
case ${name} in
"export OBJECT_FILE_DIR_normal") path1=${value} ;;
"export CURRENT_ARCH") path2=$value;;
"export SRCROOT") path3=$value;;
*)
;;
esac
done < ${path}

git_message=$(git log -1 --pretty=format:"%an:%s")
git_commit_id=$(git rev-parse --short HEAD)

git_repo="xxxx"
appversion="xxx"
buildversion="xxxx"
url="http://x.x.x.x:5000/api/cov/repo"


curl -H "Content-Type: application/json" \
-d "{\"commit_id\":\"${git_commit_id}\", \"repo\":\"${git_repo}\",  \"message\":\"${git_message}\",\"appversion\":\"${appversion}\",  \"buildversion\":\"${buildversion}\",\"object_file_dir_normal_path\":${path1},  \"current_arch_path\":${path2}, \"gcno_path\":${path3}}" ${url}

服务端接收到打包的版本号和构建号以后,同时还存入 commid 以及 commit message 等,执行 scp 命令就很简单了。对了,还需要强调一下,这里打的 iOS 包必须为Debug 包

  • 更改 *.gcno 文件内容的路径

需要保证 *.gcno 文件内的内容路径字符数,和替换后的完全相等,并不需要在 linux 上建立和 slave 同样的路径。这里的路径可以从集成到 iOS 项目工程中Pods/XcodeCoverage/env.sh里面取 SRCROOT,意思是如果 ${SRCROOT}有四位为/opt/,则在 linux 上执行的时候,需要字符相等的进行替换,替换为 linux 上存在的路径,为/ttt/

  • 覆盖率结果的合并

其实 XcodeCoverage 已经考虑到了覆盖率结果的合并,只需要在执行 XcodeCoverage 之前,指定XcodeCoverage/env.sh中 OBJECT_FILE_DIR_normal 和 CURRENT_ARCH 的值为高一级的目录,比如我想合并时间戳为 11.gcda 和 22.gcda 的覆盖率结果,那么我只需要在文件夹 33 内分别建立两个文件夹 11 和 22,然后将 11.gcda 放入 11 中,22.gcda 放入 22 中,然后将 *.gcno 分别拷入 11 和 22。执行 XcodeCoverage 命令之前,将XcodeCoverage/env.shOBJECT_FILE_DIR_normal替换为 33,CURRENT_ARCH替换为/,即可。

最后

还有一些细节,比如什么时候执行 scp 命令,什么时候执行替换 *.gcno 内的路径,什么时候执行 XcodeCoverage/getcov 命令等。

当然,覆盖率报告并不能衡量 app 的质量,它只是一个定量化的报告展示。

最后放一张我简单写的展示报告。

我想等我再整理整理,在 github 上放出我写的很 low 的代码。

ToDo

  • git diff 和覆盖率结合
  • 将结果不放在自己写的服务中,放入之前搭建的 Sonar 中
  • Android 覆盖率报告集成到这个服务
  • 继续优化当前的服务

参考文献

https://testerhome.com/topics/6644
https://github.com/jonreid/XcodeCoverage/blob/master/README.md
https://github.com/jonreid/XcodeCoverage/issues/48
https://github.com/DormyMo/SpiderKeeper

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 30 条回复 时间 点赞

@chenhengjie123 @season_fly 望指导,我简单实现了,可能还需要优化更多。

好赞呀~尤其是平台的界面效果。相比之下感觉我们自己的平台界面 low 爆了。。。

从项目的实践情况来看,到了这个程度基本可以找个项目试点了。我们现在在试点中,发现了以下几个主要问题:

  1. 实际迭代过程中,由于不断的 bug fix ,会出现一天一个版本的情况,导致单个版本覆盖率看起来都不高。这个技术上支持的难度比较高,还是得在流程上增加一个封版期,此期间不更改任何代码或不使用更改后的代码进行测试。
  2. 分析覆盖率报告并给出建议是个体力活,需要比较长的时间去熟悉源码并整理出报告。目前在尝试和开发合作,通过添加注释的方式增加报告本身的可读性,再看是否可以通过工具自动生成一些覆盖率建议。
陈恒捷 回复

跨版本合并呢?

恒温 回复

这个没找到合适的技术方案,jacoco 本身不具备这样的能力。而且覆盖率数据统计上也不好统计。你们现在有做这个?

陈恒捷 回复

jacoco 是我下一个阶段的工作,我现在已经可以把 coverage.xml 推到 sonar,不知 jacoco 这边实现覆盖率的获取坑多不多。

思寒_seveniruby 将本帖设为了精华贴 05月03日 20:03
不二家 回复

jacoco 配套做得很好,而且网上实践文章也多,坑比 iOS 少很多。

不二家 回复

话说,你 sonar 里面 oc 用的什么插件?这几天也在调研这个,今天试着部署了一个,结果导致 oc 相关的 key 冲突,server 无法解析 scanner 上传的数据,只能把插件干掉先恢复。。。

陈恒捷 回复

其实我也很懵的,我用的 oc 的插件,其实只有这一个,自己用 mvn 打的 jar 包。https://github.com/mjdetullio/sonar-objective-c,下载后,自己加了一些规则,然后 mvn 打成 jar 包,我的 sonarqube 是 5.6 的版本。

不二家 回复

看到有做 iOS 覆盖率研究的真开心,我也使用 sonar-objective-c 插件自己加规则打包集成到 sonarqube,但是没有展示出覆盖率结果,sonarqube 的版本是 5.3,你在集成覆盖率版本除了在插件添加规则之外还有做其他什么修改吗?需要修改 sonarqube 中的其他配置吗?

carrie 回复

我刚才仔细看了一下,并没有做其他修改。我也奇怪,是不是 sonarqube 版本不一样导致的呢?可以试试。感觉我自己对 sonar 的理解并不深入,只是懂皮毛。需要时间深入学习。一起加油。

不二家 回复

嗯嗯,我最近都在研究其他所以没有继续研究 sonar 了,等有空再重新试试。话说你有用到覆盖率 diff 吗?我在项目中是直接使用 lcov 的,之前也试用过 xcodecoverage,没有深入进行,后来就用 lcov 了,目前可以在 jenkins 上生成报告,考虑进一步做 diff

carrie 回复

git diff 的确是我想深入学习的地方,但是手头需要先完成安卓的手工测试覆盖率,又要去踩坑了。

你好 请问你现在实现 sonar 集成了吗

不二家 回复

你好 请问楼主你是怎么将报告转换为 coverage.xml 的

NAISI 回复

才看到,真抱歉,我已经忘记了。

您好,能否留个联系方式,有点疑问想请教一下。主要还是在 Linux 上的部署运行和 GCC 兼容问题

你好,请问一下你是如何修改 *.gcno 的文件中的路径的

faded 回复

你好,当时我是通过全路径匹配替换的。

仅楼主可见
lxs091021119 回复

需要看组件是以源码参与编译还是以 framework 参与编译

仅楼主可见
lxs091021119 回复

pods 库是不是被你生成覆盖率报告的时候过滤了?

仅楼主可见
lxs091021119 回复

我们没有针对 pods 库做这个覆盖率报告,当前也没有仔细研究。

仅楼主可见
xushuchuan 回复

我们现在为了支持 swift,不用 xcodecoverage 了。

仅楼主可见
仅楼主可见
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册