之前在做Android端测试的时候,学习了如何使用EMMA工具统计Android端的手工测试覆盖率。受到这个启发,目前在进行AR项目iOS端的测试时,也希望实现iOS端的手工测试覆盖率统计。四处询问了一些同学,暂未发现iOS端的手工测试在周围有落地的实践,于是自己抽时间找了些资料,并进行了实践,把自己的一些收获总结下,希望能和大家一起分享和讨论。
在进行iOS移动端手工测试时,测试覆盖率能够帮助我们发现一些风险和冗余。本文介绍了如何利用Xcode在真机和模拟器上开展iOS的手工测试覆盖率工作。在阐述了原理之后,详细地介绍了操作的方法和步骤,包括设置Xcode编译参数、设定生成文件目录、找到所需文件、生成报告等;进一步给出了一种改进的办法,避免了进入后台软件自动退出的缺点;最后总结了该方法的优点,以及下一步可能要开展的工作。
1. 冗余文件、冗余代码的分析。 项目经过日积月累,很有可能产生一些冗余的文件,比如替换了某种解决方案后,原方案的文件并未完全删除。冗余代码也是有可能发生的,比如工程中开发自己写的测试代码、修复bug时未将一些代码删除、拷贝的代码有些分支不会走到等。通过查看覆盖率报告,能够帮助分析以上出现的一些问题。尤其是很多类完全没有覆盖时,可能要与开发协商进行处理。
2. 测试用例未覆盖的代码。 我们知道,测试覆盖到的代码不一定是正确的,但是至少经过了一轮成功的检验,并且没有发现问题。但未覆盖到的代码就像是颗地雷,或许连一次成功的检验都不能通过,但却由于我们没有覆盖到,导致被遗漏。从这方面说,可以利用覆盖率的报告,设计一些针对性的用例,但这属于比较高级的做法,需要对代码的逻辑十分清楚。
如同Android的EMMA工具,统计iOS端手工测试覆盖率的原理也是程序插桩。Xcode自带了覆盖率编译选项,通过修改编译的参数,就能通过插桩的方式收集覆盖率文件。iOS会为每个类生成两个不同后缀的文件,这两类文件必不可少。
- gcda文件:包含覆盖率信息。会生成在真机的沙盒中,需要我们设置生成的目录。gcda文件一般是追加写入的,所以多次测试的覆盖率信息会累加起来。
- gcno文件:包含代码的结构信息。在Xcode编译项目时生成,为了防止出现问题,需要先clean已经编译的结果,重新编译,然后去指定的地方拷贝gcno文件。
这里复制一段好友文章里的话:“程序插桩,它是在保证被测程序原有逻辑完整性的基础上在程序中插入一些探针,通过探针的执行并抛出程序运行的特征数据,通过对这些数据的分析,可以获得程序的控制流和数据流信息,进而得到逻辑覆盖等动态信息,从而实现测试目的的方法。”
1、Xcode中设置编译参数,代码中设置写入gcda文件的目录。
2、clean后进行运行,安装到手机上;电脑上找到gcno文件;执行手工测试。
3、手机上拷贝出gcda文件,与gcno文件一起,使用LCOV工具生成覆盖率报告。
1、设置Xcode编译参数
我所使用的是7.3版本的Xcode。
位置 | 选项 | 值 | 备注 |
---|---|---|---|
Build Setting | Instrument Program Flow | YES | |
Build Setting | Generate Legacy Test Coverage File | YES | |
Info | Application support iTunes files sharing | YES | 可以使用iTunes或者iTools导出沙盒里数据 |
Info | Application does not run in background | YES | 按HOME键后,手机会正常退出,退出时调用__gcno_flush()函数生成gcda文件。在下面改进的方法中,可以实现不退出程序。 |
2、设置gcda覆盖率文件保存的位置。
我们可以在main文件中,设置gcda文件的位置(实际上,应该是运行到的地方可以设置位置,比如在进入后台的函数中也可以设置路径)。添加以下代码:
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsDirectory =[paths objectAtIndex:0];
setenv("GCOV_PREFIX",[documentsDirectory cStringUsingEncoding:NSUTF8StringEncoding],1);
setenv("GCOV_PREFIX_STRIP","13",1);
这样的设置,gada文件的位置是:APP沙盒/Docments/arm64.
3、编译安装: 手机连上电脑后,clean工程(Xcode上菜单栏,product–>clean),然后运行工程,将会在手机上安装app。(也可以打个ipa的包,让多个人一起测试,最后能将所有人的测试结果汇总累加起来。)
4、获取gcno文件: 前面已经成功编译了,现在我们在电脑的以下目录里去拷贝gcno文件,层级很深:
~/Library/Developer/Xcode/DerivedData/{project_dir}/Build/Intermediates/{target_name}.build/Debug-iphoneos/{target-name}.build/Objects-normal/arm64/
我的文件的位置的位置是:
/Users/netease/Library/Developer/Xcode/DerivedData/MideaSmartHouse-fpxscmzboaludwaguqxhvkzukpzr/Build/Intermediates/MideaSmartHouse.build/Debug-iphoneos/MideaSmartHouse.build/Objects-normal/arm64/
5、执行测试。 执行完成后去拷贝gcda文件:路径—APP沙盒/Docments/arm64;可以使用的工具—Xcode、iTools、iTunes
到这一步,我们已经拿到了gcno文件和gcda文件,他们是一一对应的,桌面上建个文件夹coverage,把这些文件都复制进去。接下来要做的事,就是利用工具生成HTML格式的报告。
你也可以谷歌下载CoverStory.4.4.1.dmg(点击下载),运行这个软件后,可以直接点击某个gcda文件,可以直接查看该单个文件的覆盖情况。
接着往下做:
6、安装LCOV工具,生成报告
MAC安装LCOV:
1) 打开终端,输入:
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null
2) 运行命令:
brew install lcov
安装好了!
利用lcov命令生成info文件:
格式:locv -c -d 【放gcda、gcno的文件夹】 -b 【项目工程的路径】 -o 【info文件的路径】
例如:
lcov -c -d /Users/netease/Desktop/718coverage -b /Users/netease/Documents/MideaSmartHouse-QATest -o /Users/netease/Desktop/tmp/duan719.info
利用lcov的genhtml命令生成html报告:
格式:genhtml -t 【自定义报告标题】 【info文件路径】-o 【输出报告路径】
例如:
genhtml -t "mediahouse" /Users/netease/Desktop/tmp/duan719.info -o /Users/netease/Desktop/tmp/report717
至此,报告生成了!让我们来看下报告长啥样:
补充两个命令,这两个命令都是处理已经生成的info文件:
可以过滤项目中某些文件夹,生成的报告将不会包含这些文件夹:
lcov –remove 【当前的info文件】 【希望过滤的项目中的文件夹,关键字正则匹配,多个按空格隔开】-o 【新的info文件】
例如,上面的覆盖率报告,删除了Classes文件和/Applications文件下的文件之后,生成的报告长这样:
图中可以看到,MideaSmartHouse/Modules/Test覆盖率为0。后续发现,这个文件夹是开发测试用的,可以删除。
如果有多个人进行测试,可以把他们的覆盖率整合叠加起来:
lcov -a 【第一个人的info文件】 -a 【第二个人的info文件】-a 【第三个人的info文件】…… -o 【新的info文件】
破坏了正常的软件流程。强行让软件切换到后台时退出,打乱了软件原本的流程,使得一些用例无法执行,比如AR展厅app,切换后台后程序后停止算法和相机,切换回来会重新初始化,这个过程已经发现了若干问题,是必须要测试的一个点,而我们上面的方法导致无法对该场景进行测试。
核心是什么时候来生成gcda覆盖率文件。上面的方法,软件在正常退出的时候,会自动去调用__gcov_flush()函数生成gcda文件。实际上,我们可以在任何一个代码运行到的地方来显示调用该函数,并且可以多次调用(因为gcda是追加写入的)。但是在高版本的Xcode(如Xcode 7.3)上会遇到一个问题,无法找到__gcov_flush()的引用。stack-overflow查得,高本版的Xcode不支持这么干。怎么办呢?把 __gcov_flush()函数的源码拿到,加入到项目中去。
1、下载下面的四个文件(github链接:https://github.com/liuslevis/GCDACorruptionFix)
2、添加文件。 现在我想要在软件进入后台的时候,生成文件,但不退出软件。这个是可以自己任意定制的。
先要找到软件退到后台回去执行哪个函数,然后再同一个文件夹下添加下载的文件,并且这些几个文件要添加到编译的源文件中(build phase–>compile source):
在运行函数最末尾(这里为了显示上的方便放在了函数开始的部分)加上:
NSArray*paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString*documentsDirectory =[paths objectAtIndex:0];
setenv("GCOV_PREFIX",[documentsDirectory cStringUsingEncoding:NSUTF8StringEncoding],1);
setenv("GCOV_PREFIX_STRIP","13",1);
extern void __gcov_flush();
__gcov_flush();
如果是.m文件,头文件加入#include "GCDAProfiling.h"
如果是.mm或者.cpp文件,头文件加入:
extern "C"{
#include "GCDAProfiling.h"
}
3、将Application does not run in background设置成no,clean项目后,使用一个全新的build,运行程序安装到手机上,就能实现预期目的。
包含头文件的覆盖;指标有行覆盖和方法覆盖;能显示每一行被执行了多少次;能按照行覆盖和方法覆盖进行排序,以便查找覆盖很少的部分;可以人为去除一些文件夹,不计入统计;可以多人同时进行测试,最后汇总结果……
模拟器上更简单,因为生成的gcda文件和gcno文件,默认是放在一起的,就是上面gcno文件的目录。因此不需要设置目录。
一是统计改动代码的覆盖情况。改动的代码是质量的高风险区,统计这部分的覆盖情况将十分有意义。
二是思考能否平台化。最终若能实现,给一个git仓库,就能打一个配置好的包,将会使整个过程十分简单。