作者:京东零售 邓立兵

简介

这是一个计算根据Swift & Objective-C工程的代码覆盖率的主动化脚本。之所以做成 Pod ,是便于更好的复用,该 Pod 只包含了收集生成代码覆盖率的脚本。整体比较简略便利。

这儿只讲流程,咱不讲原理。后续别的介绍

使用

1、装置:

经过CocoaPods进行装置,在你的 Podfile 文件增加如下代码:

pod 'HDCoverage'
仿制代码

然后pod install装置下载相关脚本文件。

2、关联脚本:

在项目的XcodeBuild Phases增加新的脚本(New Run Script Phase)(App在Build会履行该脚本):

"${PODS_ROOT}/HDCoverage/HDCoverage/hd_coverage_env.sh"
仿制代码

iOS代码覆盖率(一)-全量覆盖率自动化实践

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无法满足 同时兼容SwiftObjective-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会主动弹出如下目录:

iOS代码覆盖率(一)-全量覆盖率自动化实践

这儿是在项目主工程生成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退到后台

iOS代码覆盖率(一)-全量覆盖率自动化实践
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即可得到本次测验的代码覆盖率情况:

iOS代码覆盖率(一)-全量覆盖率自动化实践

点击某一个Filename区域能够查看概况,例如点击HDOCFramework.m:

iOS代码覆盖率(一)-全量覆盖率自动化实践

能够看出,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进行代码覆盖率-笔者翻译(轻喷)

本文正在参加「金石计划」