iOS系统不像Android系统这么灵活,有众多工具可以支持我们的测试,不过在覆盖率方面,也是有一定的工具的,但无法做二次开发。目前iOS主流的开发语言有Object C和Swift,下面针对这两个语系做不同的覆盖率采集分析。
2.5.1 Object C项目
Object C开发的iOS项目,使用提XcodeCoverage工具来采集覆盖率数据的,覆盖率数据是针对每个类有一个对应的gcda文件,这个文件就是覆盖率数据文件,如同Android的覆盖率数据ec文件。
一,iOS XcodeCoversage插桩
1,从网上下载Demo
下载地址: https://github.com/shabake/GHDropMenuDemo
2,编辑Podfile文件, 添加XcodeCoverage库
在项目的根目录下创建Podfile文件,添加如下内容:
platform :ios, '10.0' target 'GHDropMenuDemo' do pod 'XcodeCoverage', '~>1.0' end
执行命令pod install安装依赖库。
3,Xcode工程配置
(1) 使用Xcode打开项目,Targes -> 选择你的APP -> Build Settings -> 搜索Preprocessor Macros -> 展开在Debug一栏加入NT_COVERAGE=1
注意这里我们都只修改Debug模式下的属性, 避免影响线上版本的打包发布
(2) 同样在Build Settings中将以下3项的Debug模式改为Yes
- Generate Debug Symbols 配置成YES
- Generate Legacy Test Coverage Files 配置成YES
- Instrument Program Flow 配置成YES
(3)Build Phase中 -> New Run Script Phase -> Run Scrip中添加Pods/XcodeCoverage/exportenv.sh
这里有个注意的地方, 如果原本项目中已经有一个run script也还是新建一个
(4) AppDelegate.m中applicationDidEnterBackground方法添加以下代码:
当被测试应用进入后台时,生成覆盖率数据文件主要调用函数 __gcov_flush();
二,编译测试
1,设置应用的证书,将手机连接到电脑,安装Debug包。
2,执行手工测试用例或是自动化测试用例
注意每次完成测试后先按Home键退到后台, 等几秒让APP产生覆盖率日志不要直接杀掉APP进程
三,提取覆盖率数据
如果是在模拟器上运行测试可以跳过此步
- 打开Xcode -> window -> Devices and Simulaters, 选择运行测试的真机
- 在Installed Apps中选择测试的应用,然后点击底部的齿轮按钮选择Download Container
- 会得到一个xxxx.xcappdata文件
- 右键点击xcappdata文件 -> 显示包内容, 进入AppData/Documents/arm64/, 拷贝里面的所有.gcda文件
这里的gcda文件就是本次测试所有覆盖情况的覆盖率数据文件。
2.5.2 Swift项目
在 Swift 发展的早期,业内曾经有 SwiftCov 这样的工具可以用来生成覆盖率数据,但是随着 Swift2 原生支持了覆盖率信息,目前 Swift 的覆盖率事实上有且只有一套官方的方案,是基于 LLVM 实现的。基于官方方案的另一个好处是,配套工具相对比较成熟,比如可以生成网页,能够精确展示每行代码的执行情况,并且能够支持范型特化等多种复杂场景的统计。
一,Swift覆盖率配置
为了方便总结,我们从网上下载一个示例Demo: https://github.com/ChinaArJun/shopping-cart-Demo
项目非常很简单,直接进行编译就可以了。
1,配置 Other Swift Flags
在项目的"Build Settings"--->"Other Swift Flags"--->"Debug", 双击后面的配置项
将这两个配置添加进去:" -profile-generate 和 -profile-coverage-mapping ",注意不要改变原来的项目配置
2,设置覆盖率作用于所有的项目
"Product"--->"scheme"---->"edit scheme....",在打开的对话框中,勾选"Gather coverage for all targets".
3,添加LLVM相关定义
在swift项目中添加头文件,如:InstrProfiling.h,内容如下 :
objectivec
#ifndef InstrProfiling_h
#define InstrProfiling_h
#ifndef PROFILE_INSTRPROFILING_H_
#define PROFILE_INSTRPROFILING_H_
int __llvm_profile_runtime = 0;
void __llvm_profile_initialize_file(void);
const char *__llvm_profile_get_filename();
void __llvm_profile_set_filename(const char *);
int __llvm_profile_write_file();
int __llvm_profile_register_write_file_atexit(void);
const char *__llvm_profile_get_path_prefix();
#endif /* PROFILE_INSTRPROFILING_H_ */
#endif /* InstrProfiling_h */
创建一个 module.modulemap 文件并将所有内容导出为一个模块。
objectivec
module InstrProfiling { header "InstrProfiling.h" export * }
4,在Swift项目中调用OC内容
事实上我们不能直接创建 module.modulemap ,首先创建一个 module.c 文件然后重命名为 module.modulemap ,它还可以帮助我创建一个 ShoppingCart-Bridging-Header 文件。
(1) 创建module.c文件
(2)创建Bridging Header文件
本项目中,我们没有引用第三方的OC类,此文件中可以不写内容。如果有引用,在此文件中添加对第三类的头文件的引用。Swift文件中就可以使用oc的类及函数。
(3)设置Badging Header文件
找到Build Setting配置下的"Object-C Bridging Header"项,看后面的值是没有刚刚创建的文件,如:
如果没有,双击打开一个弹层,将左侧的Bridging文件拖过来,自动填写相应的路径。
5, 在项目中添加生成覆盖率的操作
通常我们会在应用进入后台的时候,生成覆盖率数据,所以在AppDelegate.swift下的applicationDidEnterBackground函数中,添加如下操作:
objectivec
let name = "test.profraw"
let fileManager = FileManager.default
do {
let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
let filePath: NSString = documentDirectory.appendingPathComponent(name).path as NSString
__llvm_profile_set_filename(filePath.utf8String)
__llvm_profile_write_file()
print("文件路径:"+(filePath as String))
} catch {
print(error)
}
此段代码,会在应用进入到后台时,在Documents目录下生成覆盖率数据文件,test.profraw.
二,执行测试并获取覆盖率数据
经过上面的配置后,再配置一下证书,就可以将应用打包并安装到手机上。然后执行你需要的测试用例即可,然后将应用置入后台一会儿。
1,导出覆盖率数据:选择Xcode的"windows"---->"Devices and Simulators "在打开的对应框中选择手机设备;
2,选择应用,导出应用数据
选择『shoppingCart』---"Download container" 下载应用数据。
3,打开container,查找覆盖率数据
右击文件选择『显示包内容』--->AppData-->Doucuments--->就可以找到覆盖率数据文件。
将数据文件拷到指定的文件夹,就可以生成报告。
2.5.3 总结
通过上面对Object C和Swift项目的配置,在测试过程中就能采集到测试执行对应的覆盖率数据。注意:上面的配置是针对单独应用的,如果您的应用引入了多个模块,也需要对不同的模块进行处理。
1,如果模块源码和项目源码在一起,就需要对模块进行配置,可采取如下方案,在Podfile中添加如下代码:
objectivec
#覆盖率测试需要采集数据的module
ckmodules=Array['moduellist']
post_install do |installer|
# 修改Pods中某一个模块的配置文件,好采集代码覆盖率,需要源码!
installer.pods_project.targets.each do |target|
ckmodules.each do |ckmodule|
if ckmodule == target.name
target.build_configurations.each do |config|
config.build_settings['OTHER_CFLAGS'] = '$(inherited) -fprofile-instr-generate -fcoverage-mapping'
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -profile-generate -profile-coverage-mapping'
config.build_settings['OTHER_LDFLAGS'] = '$(inherited) -fprofile-instr-generate'
end
end
end
end
end
2,如果模块源码和项目不在一起,就是第三方的模块,SDK,则覆盖率测试不需要关注它们。这些模块的功能由第三方保证,如果有问题只需要反馈给他们即可。