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

不二家的小球迷 · May 02, 2017 · Last by 不二家的小球迷 replied at April 30, 2019 · 3146 hits
本帖已被设为精华帖!

背景

事情源于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

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

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

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

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

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

跨版本合并呢?

恒温 回复

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

陈恒捷 回复

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

思寒_seveniruby 将本帖设为了精华贴 03 May 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 回复

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

Author only
lxs091021119 回复

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

Author only
lxs091021119 回复

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

Author only
lxs091021119 回复

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

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up