xcode进行代码覆盖率测试

时间:2022-07-08 15:28:08

去年写的文章,搬到cnblog

本文所述的方法只对xcode5做过测试,xcode6是否可行尚未可知。

  1. 配置编译选项

    首先请参考苹果官方的文档Configuring Xcode for Code Coverage进行相依的编译选项配置,以生成最基所需的基础数据。

    为了区别与Release&Debug的版本,建议新建一个叫做Coverage(任何名字都行)编译配置Configuration(从Debug Copy),方法在上面的文档里面有说明。

    对这个Coverage编译选项,做如下配置:(仅对Coverage做修改,不要影响到原来的Debug,Release以及Distribution

    Generate Debug Symbols 配置成YES

    Generate Test Coverage Files 配置成YES

    Instrument Program Flow 配置成YES

    做了上面的配置以后,如果为模拟器编译Coverage版本,那么可以看到~/Library/Developer/Xcode/DerivedData/{project_dir}/Build/Intermediates/{target_name}.build/Coverage-iphonesimulator/{target-name}.build/Objects-normal/i386下面,有一些.gcno文件。

  2. 收集运行时数据

    程序运行的时候,会自动记录覆盖率相关的数据,但是这个数据只有在程序正常退出的时候才会写到文件。

    对于OS X程序来说,只要通过Quit菜单退出即可;

    对与ios程序来说,要想正常的退出app,只有将info.plist里面的UIApplicationExitsOnSuspend置为YES,这样按home键就退出了,但这种方法需要干扰正常的应用逻辑,不利于维护;

    第二种方式是通过添加代码,只要在AppDelegate里面添加这样的代码即可:

   	- (void)applicationDidEnterBackground:(UIApplication *)application
{
#if NT_COVERAGE
extern void __gcov_flush(void);
__gcov_flush();
#endif
}
NT_COVERAGE是一个自定义的宏,建议在主taget的Build Setting/Preprocessor Macros为Coverage配置添加一个宏`NT_COVERAGE=1`,这样通过这个宏可以用来过滤这些专门为覆盖率测试所写的代码片段。

以模拟器为例,将app运行起来,进行一些操作,最后切换到后台,此时~/Library/Developer/Xcode/DerivedData/{project_dir}/Build/Intermediates/{target_name}.build/Coverage-iphonesimulator/{target-name}.build/Objects-normal/i386下应该有一些.gcda文件。
  1. 转换数据

    这一步是将收集的数据转换成可读的形式,使用一个开源库XcodeCoverage可以完美做到这一点:得到一组html文件,可以方便地产看每个目录下的每个类,每个函数的执行情况。

    请按照这个链接所指向的文章的指示进行配置。

    这个开源库就是一些脚本及几个很小的可执行文件,建议直接放在project的目录下即可。

    脚本下面的getcov是个bash脚本,可以进行一些必要的修改:

    1)、exclude_data函数里面,可以排除一些不想统计覆盖率的代码

    假设一些第三方库都位于library子目录,可以这样来排除

    LCOV --remove ${LCOV_INFO} "library/*" -d "${OBJ_DIR}" -o ${LCOV_INFO}

    2)、generate_report函数里面,可以更改产生html的目录

    我是这么改的,在文件的头部增加 REPORT_DIR=${DIR}/report,指定html的目录为XcodeCoverage/report

    generage_report被改成:

		generate_report()
{
"${LCOV_PATH}/genhtml" --output-directory “${REPORT_DIR}” ${LCOV_INFO}
cd ${REPORT_DIR}
open index.html
}
当完成数据收集后,运行./getcov命令即可。
  1. 如何针对ios设备进行覆盖率测试

    与模拟器相比,区别的地方在于测试数据输出的位置,因为默认的位置在设备上是不可写的,因此需要在appDelegate里面做一些修改:
		- (void)applicationDidEnterBackground:(UIApplication *)application
{
#if NT_COVERAGE
#if !TARGET_IPHONE_SIMULATOR
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
setenv("GCOV_PREFIX", [documentsDirectory cStringUsingEncoding:NSUTF8StringEncoding], 1);
setenv("GCOV_PREFIX_STRIP", "13", 1);
#endif extern void __gcov_flush(void);
__gcov_flush();
#endif
}
第一个setenv的意思是将数据的根目录设置为app的Documents;
第二个setenv的意思是strip掉一些目录层次,因为覆盖率数据默认会写入一个很深的目录层次。 在完成app测试之后,将这些.gcda文件拷贝到对应.gcno文件同样的目录,在我的测试case下是~/Library/Developer/Xcode/DerivedData/{project_dir}/Build/Intermediates/{target_name}.build/Coverage-iphoneos/{target-name}.build/Objects-normal/armv7。然后运行上面3中getcov命令即可。
  1. 与单元测试相结合

    XcodeCoverage介绍了如何在单元测试后自动生成覆盖率测试报告,只是一个脚本配置而已。

    需要注意的是,如果使用单元测试,那么单元测试target也必须使用上文所说的Coverage编译配置项,因为运行单元测试时,会自动编译主target的同名编译配置项。

    这个开源工具是通过run_code_coverage_post.sh来在test执行完毕之后来启动覆盖率数据收集的,我修改了一下这个脚本,打开一个终端窗口来打印执行过程:

		#!/bin/bash
button=`/usr/bin/osascript <<EOT
tell application "Finder"
activate
set dialogText to "Generate code coverage report?"
set cancelText to "Cancel"
set okText to "OK"
set myReply to button returned of (display dialog dialogText buttons
{cancelText, okText} cancel button cancelText default button okText)
end tell
EOT`
if [[ $button = "OK" ]]; then
`/usr/bin/osascript <<EOT
tell application "Terminal"
close back window
do script "/bin/bash ${SRCROOT}/XcodeCoverage/getcov"
end tell
EOT`
fi
echo "Done.”

代码覆盖率的结果,可以用来指导开发者进行充分的调试,或是评估单元测试的case是否足够;第一次完成上面的配置之后,以后的使用是很方便的。

另外需要指出的几点:

1)__gcov_flush在一次测试中是可以被反复调用的,可以边测试边看覆盖率;

2)本文对XcodeCoverage所做的几处修改,在这里可以找到;

3)另外每次覆盖率测试最好用一个全新的build,因此需要将上次build的中间文件全部删除,可以打开Xcode的Product菜单,按住Option键,点击Clear Build Folder…。