xctest 相关总结

你下去研究,可以按这张知识地图来,不要散着看。

1. 单元测试层

先搞清楚测试框架负责什么:

复制代码
XCTest
  Apple 官方单元测试框架。
  负责发现测试方法、执行测试、断言结果。

测试用例
  负责调用业务代码,让目标函数、分支、异常路径真的运行。

Mock / Fake / Stub
  测试替身。
  用来替代真实依赖,控制返回值、制造异常、隔离外部环境。

重点结论:

复制代码
XCTest 不计算覆盖率。
Mock 不计算覆盖率。
它们负责让业务代码执行到指定路径。

2. 覆盖率计算层

覆盖率总公式:

复制代码
覆盖率 = 已执行数量 / 纳入统计的总数量

常见指标:

复制代码
行覆盖率
  被执行过的可执行代码行 / 纳入统计的可执行代码行总数

函数覆盖率
  被调用过的函数 / 纳入统计的函数总数

分支覆盖率
  被执行过的 true/false 分支路径 / 纳入统计的分支路径总数

Region 覆盖率
  LLVM 更细粒度的代码区域覆盖率

重点结论:

复制代码
函数覆盖了,不代表分支都覆盖。
行覆盖了,也不代表所有条件路径都覆盖。
分支覆盖率比行覆盖率更严格。

3. 编译插桩层

Xcode 打开覆盖率:

复制代码
xcodebuild test -enableCodeCoverage YES

底层 clang 会带:

复制代码
-fprofile-instr-generate
-fcoverage-mapping

含义:

复制代码
-fprofile-instr-generate
  生成覆盖率计数器和运行时采集逻辑。

-fcoverage-mapping
  生成源码位置和计数器之间的映射关系。

流程:

复制代码
Objective-C 源码
  ↓
Clang 解析
  ↓
LLVM IR
  ↓
LLVM 插桩
  ↓
机器码 / 二进制
  ↓
XCTest 执行

重点结论:

复制代码
插桩不是改 .m 源码。
插桩是在编译过程中给中间代码/二进制加入计数器。
测试运行到哪里,对应计数器就 +1。

4. 运行采集层

测试运行时:

复制代码
XCTest 调用业务代码
业务代码执行到函数/行/分支
LLVM 计数器增加
测试结束写出 .profraw
Xcode/llvm-profdata 合并为 Coverage.profdata

报告生成需要两类东西:

复制代码
测试二进制
  保存源码映射、函数信息、覆盖率 section。

Coverage.profdata
  保存运行后的计数结果。

重点结论:

复制代码
profdata 单独不是完整报告。
llvm-cov 需要"测试二进制 + profdata"才能还原源码覆盖率。

5. 报告工具层

常见文件:

复制代码
xccov-report.txt
  Xcode 行覆盖率视图,适合快速看文件/方法覆盖率。

llvm-cov-report.txt
  LLVM 汇总报告,能看 region、function、line、branch。

OCBillingCalculator.llvm-cov.txt
  源码逐行报告,能看每行执行次数和分支 true/false 次数。

cobertura.xml
  给 GitLab/Jenkins 读取的机器报告,不适合直接当完整人工报告。

HTML 报告
  给人看的详细覆盖率报告。

重点结论:

复制代码
CI 读 cobertura.xml。
人看 llvm-cov HTML / genhtml / 源码逐行报告。

6. 统计口径层

一定要区分:

复制代码
代码执行了
代码进入覆盖率分母

它们不是一回事。

通常业务覆盖率要排除:

复制代码
测试代码
Mock/Fake 辅助类
三方库
系统库
build 目录
生成代码
main/AppDelegate 等非核心业务入口

重点结论:

复制代码
排除分母不是说代码不能运行。
而是说它不属于业务覆盖率考核对象。

7. 常见争议点

Mock:

复制代码
Mock 驱动到的业务代码会计入覆盖率。
Mock 对象自己的测试辅助代码通常不纳入业务覆盖率分母。

try/catch:

复制代码
catch 不是不能覆盖。
没有异常用例时不会覆盖。
通过 mock 抛异常或构造异常输入,可以覆盖 catch。

#if DEBUG:

复制代码
这是编译期条件。
当前构建没编译进去的分支,不进入当前覆盖率分母。
Debug 跑不到 Release 的 #else,因为它根本不在 Debug 产物里。

75%:

复制代码
能不能达到取决于统计口径。
业务代码行覆盖率 75% 是可以做到的。
全工程含三方库/系统代码/老旧不可测代码的分支覆盖率 75% 可能很难。

不可覆盖代码:

复制代码
真正不可达代码
强依赖硬件/系统环境
异常路径不可制造
多线程竞态
编译条件分支
设计不可测试

处理方式:

复制代码
补用例
Mock/Fake
依赖注入
重构
排除分母
记录合理未覆盖风险

8. 你重点研究这些关键词

复制代码
XCTest
Mock / Fake / Stub
Code Coverage
Line Coverage
Function Coverage
Branch Coverage
LLVM Source-based Code Coverage
Clang
LLVM IR
Instrumentation
-fprofile-instr-generate
-fcoverage-mapping
profraw
Coverage.profdata
llvm-profdata
llvm-cov show
llvm-cov report
xccov
Cobertura XML
coverage denominator
exclude / ignore filename regex

9. 官方资料优先看

建议顺序:

复制代码
1. Clang Source-based Code Coverage
2. llvm-cov command guide
3. Apple XCTest / Xcode Code Coverage / xccov
4. GitLab Cobertura coverage_report
5. Jenkins Coverage plugin / Cobertura / LCOV 支持

10. 你现场要记住的总框架

复制代码
测试层:
XCTest / Mock 负责让业务代码跑到。

编译层:
Clang / LLVM 负责插桩。

运行层:
计数器记录执行次数,生成 profraw/profdata。

报告层:
llvm-cov / xccov / GitLab 负责展示。

口径层:
通过 ignore/filter 控制哪些代码进入业务覆盖率分母。

最重要的一句话:

复制代码
覆盖率统计的是:当前编译产物中、纳入统计范围的可执行代码,在测试运行时有没有被执行到。