iOS代码笼盖率(一)-全量笼盖率主动化理论
做者:京东零售 邓立兵
简介
那是一个统计基于 Swift Objective-C 工程的代码笼盖率的主动化脚本。之所以做成 Pod ,是便于更好的复用,该 Pod 只包罗了搜集生成代码笼盖率的脚本。整体比力简双方便。
那里只将流程,咱不讲原理。后续别的介绍
那里只将流程,咱不讲原理。后续别的介绍
利用
1、安拆:
通过 CocoaPods 停止安拆,在你的 Podfile 文件添加如下代码:
pod 'HDCoverage' 复造代码
然后 pod install 安拆下载相关脚本文件。
2、联系关系脚本:
在项目标 Xcode 的 Build Phases 添加新的脚本(New Run Script Phase)(App在Build会施行该脚本):
"${PODS_ROOT}/HDCoverage/HDCoverage/hd_coverage_env.sh" 复造代码
3、工程设置装备摆设代码笼盖率参数:
那里原来是在 HDCoverage 有脚本撑持的,但是基于对哪些模块(Pod做为独立模版)停止代码笼盖率,所以定见在 Podfile 自主添加如下代码乖巧治理,详尽阐明如下:
展开全文
# 实现post_install Hooks
# 需要搜集Code Coverage的模块
ntargets = Array['AFNetworking']
require 'xcodeproj'
post_install do |installer|
# 修改Pods中某一个模块的设置装备摆设文件,好摘集代码笼盖率,需要源码!
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if(config.name = 'Release') == 0
config.build_settings['OTHER_CFLAGS'] = '$(inherited)'
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited)'
config.build_settings['OTHER_LDFLAGS'] = '$(inherited)'
ntargets.each do |ntarget|
if(ntarget = target.name) == 0
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'
break
end
end
else
config.build_settings['OTHER_CFLAGS'] = '$(inherited)'
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited)'
config.build_settings['OTHER_LDFLAGS'] = '$(inherited)'
end
end
end
# 修改主工程
project_path = './HDCoverage.xcodeproj'
project = Xcodeproj::Project.open(project_path)
puts project
project.targets.each do |target|
if(target.name = 'HDCoverageDemo') == 0
target.build_configurations.each do |config|
if ((config.name = 'Release') == 0 || (config.name = 'Debug') == 0)
# 设置预编译变量CODECOVERAGE
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) CODECOVERAGE=1'
# OC代码笼盖率插桩设置装备摆设
config.build_settings['OTHER_CFLAGS'] = '$(inherited) -fprofile-instr-generate -fcoverage-mapping'
# Swift代码笼盖率插桩设置装备摆设
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -profile-generate -profile-coverage-mapping'
# 摘集代码笼盖率设置装备摆设
config.build_settings['OTHER_LDFLAGS'] = '$(inherited) -fprofile-instr-generate'
# Release需要设置,否则无法解析代码笼盖率
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Onone'
else
config.build_settings['OTHER_CFLAGS'] = '$(inherited)'
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited)'
config.build_settings['OTHER_LDFLAGS'] = '$(inherited)'
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = ''
end
end
end
end
project.save()
end
4、代码施行数据搜集:
利用 GCC 无法称心 同时兼容 Swift和 Objective-C ,所以那里是基于 LLVM 停止,官网文档 。也能够参考笔者翻译的 Source-based Code Coverage ,完全详尽的教程能够看 Source-based Code Coverage for Swift Step by Step。
4.1、起首在工程中申明 LLVM 几个关键的函数:
#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);
void __llvm_profile_set_filename(const char *);
int __llvm_profile_write_file(void);
int __llvm_profile_register_write_file_atexit(void);
const char *__llvm_profile_get_path_prefix(void);
#endif /* PROFILE_INSTRPROFILING_H_ */
4.2、再次封拆代码笼盖率相关API,便于上层更好利用(定见):
class HDCoverageTools: NSObject { static var shared = HDCoverageTools()
// 重视:动态库是需要零丁注册,而且需要在动态库中施行\_\_llvm\_profile\_write\_file()
func registerCoverage(moduleName: String) {
let name = "\\(moduleName).profraw"
print("registerCoverage, moduleName: \\(moduleName)")
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
print("HDCoverageGather filePath: \\(filePath)")
\_\_llvm\_profile\_set\_filename(filePath.utf8String)
} catch {
print(error)
saveAndUpload()
// 适宜的时机代码笼盖率上报
func saveAndUpload() {
\_\_llvm\_profile\_write\_file()
4.3、启动时刻,注册代码笼盖率API:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) - Bool {
HDCoverageTools.shared.registerCoverage(moduleName: "HDCoverageDemo")
return true
4.4、在适宜的时刻(根据各人的营业场景)将笼盖率数据写进造定的途径:
func sceneDidEnterBackground(_ scene: UIScene) {
// 笔者那里测试,是在App进进后台后写进
DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) { [self] in
HDCoverageTools.shared.saveAndUpload()
5、测试以便生成笼盖率数据:
以本工程的demo为例阐明
5.1、在运行胜利后,Finder 会主动弹出如下目次:
那里是在项目主工程生成 CoverageResult 目次,而且将生成代码笼盖率可视化的脚本 hd_parse_profraw.sh 拷贝到那里,将 项目 HDCoverageDemo.app 拷贝过来,次要是获取其 Mach-O :
$ tree -L 2
├── MachOFiles
│ └── HDCoverageDemo.app
├── Profraw
└── hd_parse_profraw.sh
5.2、施行测试用例,那里我别离点击了:"主工程(OC)-Case1/Case2"、"主工程(Swift)-Case2/Case3"、"Framework(OC)-Case1/Case2"、"FrameworkSwift)-Case2/Case3" 后,App退到后台
5.3、查看掌握台,能够看到 profraw 文件:
registerCoverage, moduleName: HDCoverageDemo
HDCoverageGather filePath: /Users/denglibing/Library/Developer/CoreSimulator/Devices/5D01D4AA-40AE-4FC6-845C-391A94828EE3/data/Containers/Data/Application/283906A5-1681-44A5-8522-126D29D2F148/Documents/HDCoverageDemo.profraw
将 HDCoverageDemo.profraw 拷贝到 CoverageResult/Profraw 目次中;
5.4、施行 hd_parse_profraw.sh 脚本:
$ tree -L 2
├── MachOFiles
│ └── HDCoverageDemo.app
├── Profraw
│ └── HDCoverageDemo.profraw
└── hd_parse_profraw.sh
$ sh hd_parse_profraw.sh
CoverageResult: /Users/denglibing/HDProject/iOSProject/SProject/hdcoverage/Example/CoverageResult/CoverageResult
machOFiles: /Users/denglibing/HDProject/iOSProject/SProject/hdcoverage/Example/CoverageResult/MachOFiles
/Users/denglibing/HDProject/iOSProject/SProject/hdcoverage/Example/CoverageResult/CoverageResult 不存在,已经创建
disposeProfrawFiles profraws: /Users/denglibing/HDProject/iOSProject/SProject/hdcoverage/Example/CoverageResult/Profraw
disposeProfrawFiles profraw file: HDCoverageDemo.profraw
findMachOFileName: HDCoverageDemo
findMachOFilePath: /Users/denglibing/HDProject/iOSProject/SProject/hdcoverage/Example/CoverageResult/MachOFiles/HDCoverageDemo.app/HDCoverageDemo
disposeProfrawToHtml, machoFileName: HDCoverageDemo machOFilePath: /Users/denglibing/HDProject/iOSProject/SProject/hdcoverage/Example/CoverageResult/MachOFiles/HDCoverageDemo.app/HDCoverageDemo
施行胜利后将主动将可视化的代码笼盖率目次翻开:
$ tree -L 3
├── CoverageResult
│ └── HDCoverageDemo
│ ├── coverage
│ ├── index.html
│ └── style.css
├── MachOFiles
│ └── HDCoverageDemo.app
│ ├── Base.lproj
│ ├── Frameworks
│ ├── HDCoverageDemo
│ ├── Info.plist
│ ├── PkgInfo
│ └── _CodeSignature
├── Profraw
│ ├── HDCoverageDemo.profdata
│ └── HDCoverageDemo.profraw
└── hd_parse_profraw.sh
9 directories, 8 files
复造代码
5.5、查看:翻开 CoverageResult/HDCoverageDemo/index.html 即可得到本次测试的代码笼盖率情状:
点击某一个 Filename区域 能够查看详情,例如点击 HDOCFramework.m:
能够看出,tag == 3 的代码行数并没有施行到,那正和上面测试的 "Framework(OC)-Case1/Case2" 契合。
小结
全量代码笼盖率能够搀扶帮助开发者聚焦变更代码的逻辑缺陷,从而更好地制止线上问题。那里更多的是讲述基于 Swift Objective-C 工程的 全量代码笼盖率 的计划,没有原理,只要简单的流程。半途测验考试过多个计划,最末依靠 Cocoapods 才能将主动化脚本赋能出往。
但是现实开发过程,不成能每次都往存眷 全量代码笼盖率,下一篇陆续介绍:iOS代码笼盖率(二)-增量笼盖率主动化理论
Demo及脚本源码地址,欢送批示+Star
参考
Source-based Code Coverage for Swift Step by Step : 十分详尽的Swift代码笼盖率教程,收获颇丰。
iOS 基于非Case的Code Coverage系统搭建 : 基于对OC项目标代码笼盖率介绍,供给了脚本化构想,收益匪浅。
llvm-profdata - Profile data tool: 用于处置生成profdata号令
Source-based Code Coverage :llvm官网基于源码对Swift和OC停止代码笼盖率
Source-based Code Coverage 中文版 :llvm官网基于源码对Swift和OC停止代码笼盖率-笔者翻译(轻喷)