作者:京东批发 邓立兵
简介
这是一个统计基于 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_
// https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
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 进行代码覆盖率 - 笔者翻译(轻喷)