Flutter鸿蒙化覆盖率报告清洗:remove_from_coverage适配与CI质量门禁实践
覆盖率报告里躺着几十个没用的文件我决定对 remove_from_coverage 动一次鸿蒙化手术去年我们在做 Flutter 鸿蒙化改造时测试覆盖率报告一直处于一种看似有用、实则没人敢用的状态。flutter test --coverage跑完打开 lcov 报告里面躺着大量自动生成的.g.dart、.freezed.dart、generated_plugin_registrant.dart甚至还有第三方 SDK 的封装层代码。整个工程的覆盖率数字虚高但真正审计业务代码的时候根本没法定位到底哪块逻辑没被测试覆盖到。后来我们把remove_from_coverage这个三方库引入进来做报告精简化效果立竿见影。这篇文章就把这套鸿蒙化适配的完整过程拆开讲清楚从工具原理、适配改造到 CI 质量门禁全部是实操记录。1. 覆盖率报告里的统计幻觉为什么数字漂亮却没人敢用1.1 一场关于覆盖率 86%的审计现场我记得特别清楚今年年初有一次版本质量评审测试同学把覆盖率报告甩到群里上面写着工程整体行覆盖率 86%分支覆盖率 79%。乍一看数据很漂亮但代码评审的时候负责核心支付链路的同事直接摇头支付模块的核心状态机逻辑单元测试压根没覆盖到几个分支但整体覆盖率还是被拉得很高。问题就出在报告本身的含水量上。Flutter 工程默认生成的 lcov.info会把工程里所有参与编译的.dart文件的覆盖率记录都收进去这里面包含大量不需要关注的文件。真实业务代码可能只有几万行但报告里统计的基数可能是几十万行。分母被撑大了分子稍微跑一跑覆盖率数字自然就上去了。这种数据拿去给管理层看还行但拿去指导测试策略、定位风险模块完全是在误导决策。1.2 报告里最常见的四类噪声文件我统计了一下手里几个 Flutter 工程噪声文件基本来自四个方向每一类都有典型的路径特征文件类别典型路径模式为什么不该计入覆盖率代码生成产物*.g.dart、*.freezed.dart、*.pb.dart全部由 build_runner 或 protoc 生成改模板才有意义插件注册文件generated_plugin_registrant.dart纯模板化注册逻辑无业务价值三方 SDK 封装层.dart_tool/、~/.pub-cache/下引入的包是依赖方代码不归当前工程审计平台桥接样板*.plugin.cipd.dart、*.stub.dart平台通道的固定写法测了也没什么指导意义这些文件混在报告里最直观的影响是单个文件覆盖率明细变得极其冗长。开发者在 Codecov 或者 SonarQube 上打开报告一眼望过去全是生成代码真正想看的业务代码要翻好几页才找得到。时间一长大家就懒得看覆盖率报告了覆盖率数据就变成了一个每次发版前跑一下、截个图、发个邮件的形式主义产物。1.3 数据失真背后的治理难题别小看这个问题。覆盖率数据一旦失真后续一系列质量动作都会跟着变形。比如我们当时在做新代码必须达到 80% 行覆盖的合并请求门禁但因为报告里有大量生成代码的干扰经常出现这种情况开发者只给某个_internal.dart文件加了几行测试整个合并请求的覆盖率就被拉起好几个百分点门禁轻松通过。但实际上这次改动引入了一个新的状态分支这个分支压根没有测试覆盖。门禁形同虚设因为指标已经被噪声污染了。所以remove_from_coverage这种工具存在的真正价值不只是让报告好看一点而是让覆盖率数据重新变得可审计、可决策。这也是我们下决心做鸿蒙化适配的出发点质量基建里的工具链不能因为换了操作系统底座就断档。2. remove_from_coverage 的过滤机制LCov 数据流里的三次裁剪2.1 这个工具的核心定位与应用场景remove_from_coverage是一个纯 Dart 实现的命令行工具定位非常明确在flutter test --coverage生成 lcov.info 之后、上报到覆盖率平台或者生成 HTML 报告之前对覆盖率记录做一次清洗。它的使用方式也非常轻量。常见的调用流程是这样flutter test --coverage dart run remove_from_coverage --config tool/coverage_exclude.yaml配置文件里声明要排除的文件模式它就会读取coverage/lcov.info把匹配到的文件对应的覆盖率记录全部摘除然后写回或生成新的 lcov 文件。整个过程不碰测试本身也不改代码是一个纯后处理步骤。在标准 Flutter 工程里这个工具几乎零成本接入。但到了鸿蒙化场景事情就没那么简单了因为生成覆盖率报告这整条链路的环境已经变了。2.2 它在背后做的三件事要理解鸿蒙化适配到底改了什么先得知道这个工具在幕后干了什么。拆开来看它每次运行主要做三件事第一解析工程文件索引。它会读取.dart_tool/package_config.json拿到当前工程所有包的根路径。这一步很关键因为只有知道了哪些代码属于当前工程、哪些属于依赖包才能在匹配路径时做好边界判断。第二按规则匹配并剔除记录。lcov.info 里的每一条文件记录都包含文件路径和数据块DA 开头的行工具把路径和配置规则做匹配凡是命中的文件整块记录直接剔除。一直到这里还是一个过滤的概念。第三重写覆盖率汇总索引。剔除之后报告末尾的Summary部分要同步更新否则工具生成的报告平台读不了。很多人在自定义工具时容易漏掉这一点导致上游平台解析失败。本质上它就是一个 LCov 格式的数据清洗管道。理解了这条管道再看鸿蒙化场景就很容易判断该在哪里动刀了。2.3 鸿蒙化之后原有假设全被打破了标准 Flutter 工程和鸿蒙化 Flutter 工程底层跑的工具链长得像但细节差异非常大。remove_from_coverage在设计时是基于标准 Flutter 工程的环境假设的鸿蒙化以后至少有三个假设被打破了。第一个假设是测试命令和产物路径。标准 Flutter 在coverage/lcov.info下生成报告鸿蒙化之后测试命令可能走的是 OpenHarmony 定制的测试通道产物的存放路径、文件命名规则都和原先不一样。第二个假设是依赖解析的方式。.dart_tool/package_config.json在鸿蒙化 SDK 下还是存在的但内容结构有了变化比如鸿蒙SDK本身的包路径、以及Fusion等工具的介入路径。工具如果写死了字段名适配起来就要改。第三个假设是跨平台路径分隔符。我们用 Linux 的 CI 节点跑鸿蒙化测试但开发机可能是 Windows。路径分隔符不一致会导致匹配规则在某个环境下失效。这个坑我们后面踩得很深放到第三章专门讲。3. 鸿蒙化适配实践从跑不通到可信任的覆盖率报告3.1 环境准备鸿蒙化 Flutter 工程的测试基建在动手改工具之前得先把测试环境跑通。我建议先按这个清单挨个核对缺一项都可能让适配工作白费OpenHarmony SDK 版本与 Flutter 鸿蒙化分支要匹配不同配套版本生成的覆盖率数据格式有差异flutter test --coverage在前置环境下能正常生成lcov.info工程里存在可运行的纯 Dart 测试且跑的是鸿蒙化 SDK 对应的 Dart 运行时本地能访问.dart_tool/package_config.json并且能正常解析准备好 Linux 和 Windows 各一台环境用于验证跨平台行为这里面最容易被忽略的是第一条。OpenHarmony SDK 小版本之间的测试链路差异可能导致 lcov.info 的生成方式完全不同。我们当时就踩过SDK 从 5.0.0 升到 5.0.1 之后coverage/目录下的临时文件结构变了工具解析路径的逻辑直接失效。3.2 三个关键改造点路径、解析、依赖加载3.2.1 路径分隔符的跨平台适配remove_from_coverage在匹配文件路径时用的模式通常是 Glob 风格**/*.g.dart这种写法看起来挺通用但实际执行时如果工具内部把路径直接拿来做字符串比较那么在 Windows 下就会出现lib\core\http_client.g.dart和lib/core/http_client.g.dart不一致的情况。我们的做法是在工具入口层加一个统一的路径标准化函数把所有读到的文件路径统一转成使用正斜杠的形式然后再进入匹配流程。String normalizePath(String rawPath) { return rawPath.replaceAll(\\, /); }这个改动虽然小但解决了 80% 的换个机器规则就失效问题。剩下的 20%是发生在.dart_tool/package_config.json里的路径格式上这个后面单独说。3.2.2 package_config.json 的字段变化处理鸿蒙化 SDK 的 Flutter 分支package_config.json里的 rootUri 和 packageUri 字段跟标准版有细微差别。更麻烦的是鸿蒙SDK内置的一些包路径前缀不是file://而是ohos://之类的自定义协议头。如果工具在解析 rootUri 时直接按file://的前缀去剥遇到ohos://开头的记录就会解析出错误路径。我们改进了解析逻辑先判断协议头再决定是否走文件系统路径映射凡是不能被识别为本地文件路径的包直接跳过匹配不纳入最终写回的范围。3.3 踩坑链路从报告没变化到定位问题的完整过程适配过程中有一段时间规则明明配了但生成的报告纹丝不动该出现的生成代码还是躺在里面。这个问题的排查过程挺典型我把链路完整写出来大家可以作为参考去排查自己的工具问题。第一步我在配置里加了路径匹配规则remove: - **/*.g.dart - **/*.freezed.dart运行后报告没变化。我的第一反应是规则没生效于是加了 debug 日志打印工具到底读到了哪些文件、匹配到了哪些文件。结果发现工具读到的文件路径列表本身就为空。第二步定位为空的原因。检查发现工具读取 lcov.info 时用的还是标准 Flutter 的路径coverage/lcov.info但鸿蒙化环境下测试产物被放在了build/ohos/test-coverage/lcov.info。路径不对数据都没读进来规则自然没有效果。第三步定位到问题后我直接把读取路径参数化做成命令行参数dart run remove_from_coverage --config tool/coverage_exclude.yaml --lcov-path build/ohos/test-coverage/lcov.info这之后报告开始有变化了但新的问题又冒出来路径匹配规则在某些情况下仍然失效。仔细排查发现是第三节说的路径分隔符问题。在 Linux CI 节点上正常在 Windows 开发机上跑出来的报告却完全没有过滤效果。加上统一标准化处理后才彻底解决。这个排查链路给我的启发是鸿蒙化适配遇到问题时一定要先验证数据链路的每一站——数据有没有读进来、匹配有没有跑起来、写回有没有成功。不要直接扎进规则配置里去调八成不是规则本身的问题。4. 把覆盖率精简化落到 CI质量门禁与治理闭环4.1 排除规则的制定原则白名单优于黑名单有同学会问既然要排除文件是不是把生成代码全部加进黑名单就行了我的建议是反过来用白名单的思路来定规则。具体做法是先让工具把所有文件都打出来然后人工审核一遍确定哪些目录是真正的业务代码再针对这些目录做覆盖率统计。其他的一律排除。好处有两个一是规则清晰不会出现漏了一个生成目录又飘在报告里的情况二是基层开发同学好理解一看到规则就知道只有这些目录是考核对象。我们当前工程的排除示例remove: - **/*.g.dart - **/*.freezed.dart - **/*.pb.dart - **/*.grpc.dart - **/generated_plugin_registrant.dart - **/.dart_tool/** - **/ohos/**注意最后一条鸿蒙化之后会有一些平台相关的桥接代码放在ohos/目录之下这些文件在标准 Flutter 工程里是不存在的适配时一定要单独加上。4.2 质量门禁配置从看整体到盯增量清理完报告里的噪声之后覆盖率数据终于可以拿去当质量门禁了。但我的建议是不要一上来就定一个过高的整体阈值那样容易引发为了凑覆盖率而写测试的恶性循环。更好的做法是盯两个指标增量覆盖率本次合并请求新增代码的覆盖率低于 70% 直接拦截模块覆盖率离散度全量覆盖率报告按模块拆分之后看哪一个模块明显低于整体均值单独提出来做专项补充增量覆盖率的数据从哪里来两个基础第一分支和主干合并后用 diff 工具比对出新增的代码行范围第二用清洗后的 lcov.info 做行级映射。这两个数据在 CI 流程里都能拿到关键是要把remove_from_coverage的过滤结果作为共享的中间产物分发给下游所有质量工具使用。4.3 把清洗后的报告接入现有平台我们用的是自建的覆盖率展示平台只是把remove_from_coverage的输出替换了原来的lcov.info其他流程几乎不用动。如果你们用的是 SonarQube 或者 Codecov只需要在配置里替换上传的文件路径即可。接入后发现一个实实在在的好处报告页面的加载速度快了很多。之前一个中型工程的 lcov.info 有十几 MB清洗后可能只剩 3-4 MB页面打开不再卡顿。开发者查看单文件覆盖率时也不会被一长串生成代码干扰注意力体验提升非常明显。5. 从过滤工具到数字化底座覆盖率治理的下一步5.1 数据可信之后才谈得上治理工具适配完成只是第一步真正的价值在于把覆盖率数据变成数据化治理的基石。我们后续做了三件事都是在清洗后的数据基础上展开的第一分模块的覆盖率台账。每个业务模块单独出覆盖趋势图按月复盘。哪个月覆盖率掉得厉害对应的需求发布列表、代码变更记录都能拉出来对照问题归因效率明显提升。第二核心链路的重点审计清单。付款、登录、数据同步这几条核心链路单独建立审计机制每次发版前必须输出这些链路的覆盖率快照。因为报告里没有了噪声的干扰快照数据足够干净可以直接拿去和上一版本对比。第三测试用例质量的反向校验。当某个模块的覆盖率很高但线上 bug 率也不低时我们有理由怀疑测试用例断言写得不够严谨。覆盖率清洗后再看这个数据得出的结论才具备可信度也能推动测试团队优化用例。5.2 后续扩展方向与个人建议这套适配做完之后我们还规划了三个扩展方向写出来供大家参考把过滤规则下沉到统一的配置文件里由质量效能团队集中管理业务团队不能随意改动增加覆盖率报告变更对比能力每次自动化生成后自动 diff过滤规则调整时能直观看到报告变化把鸿蒙化适配的补丁回馈给开源社区让更多做鸿蒙化的团队能直接复用一个成熟的工具版本最后分享一点个人的经验鸿蒙化适配表面上是在改代码实际上是在重新建立一套在这条新链路下依然可信的质量度量体系。工具只是手段让覆盖率数据在治理层面重新变得干净、可用、可问责才是这次适配的全部意义。希望你做鸿蒙化的时候不需要再趟一遍我们踩过的坑。