Skip to content

复杂系统代码质量-测试先行 ​

看在前面 ​

面对庞大复杂的系统,保障代码质量的答案是让测试先行。把单元测试作为代码质量的第一道防线,用增量代码的测试覆盖率卡点挡住劣化。再逐步治理存量。本文是一家技术团队(阿里系团队实践)的完整方法论。

一、为什么要做单元测试 ​

质量保障:复杂系统动辄几百个服务、几千万行代码,链路一长,QA 的黑盒测试根本覆盖不过来。单测是成本最低的验证手段,能在编码阶段就发现逻辑错误。缺陷发现越早,修复成本越低(需求期修复成本 1 倍,上线后修复成本可能是百倍)。

重构与演进的前提:没有单测保护的代码没人敢动,系统只能腐化不能演进。有单测兜底,重构才有安全网。

新人融入:单测是最好的代码文档,新人可以通过编写/阅读单测快速理解模块的行为边界。

两个真实案例:

  • 跨境资金链路事故:一笔资金操作因未校验币种,人民币交易被当成美元处理,直接造成资损。这类边界条件靠人肉测试很难发现,一条单测就能拦住;

  • IM 文件校验:文件上传的校验逻辑改动后,大小写后缀(.JPG)漏校验导致部分文件打不开。回归时跑一遍已有单测即可暴露。

二、如何做单元测试:先学会提炼"单测点" ​

复杂系统里不可能所有代码都有单测,先从稳定的核心逻辑提炼测试点:

  • 网关层/MQ Listener:外部入口的参数校验、转换逻辑;

  • 定时任务(JOB):任务触发的核心分支;

  • Service 私有方法:承载业务规则判断的私有方法,通过反射调用测试;

  • 数据读写层:DAO 的 SQL 正确性、字段映射;

  • 工具类/公共组件:金额计算、日期处理这类纯函数最容易写单测,收益也最稳定。

提炼优先级的判断标准:改得多、出问题影响大、逻辑分支密集的代码优先覆盖。

配套的基于 Spring 的单测实践:

  • XML + 注解自动注入,只加载被测模块相关的 Spring 上下文,不启动全量容器;

  • DAO 层用 DaoMock + DbUnit,Mock 数据用 Excel 维护(一行一个场景),可读性和维护性都好;

  • TestRunner 缓存 Spring 上下文:多个测试类复用同一份容器,避免每个类都重新启动。把单测耗时从分钟级压到秒级。

三、单元测试规范 ​

  • 命名:测试类名 = 被测类名 + Test;方法名 = test + 被测方法 +_场景_预期结果,让失败报告能直接读出场景;

  • AIR 原则:Automatic(全自动执行)、Independent(相互独立,不能依赖执行顺序)、Repeatable(可重复执行,结果稳定);

  • BCDE 原则:Border(边界值,循环边界、日期边界)、Correct(正确的输入,得到预期输出)。Design(按设计文档的分支逐条覆盖)、Error(异常输入,得到预期错误);

  • 单测 vs 集成测试:单测mock 掉外部依赖只验证自身逻辑。涉及中间件真实交互的用集成测试,分层治理;

  • 断言精简:一个测试方法聚焦验证一个行为,避免大而全的"流水账断言"。

四、如何推进落地:增量为先、存量为辅 ​

增量把控(核心手段):

  • 新增代码必须带单测,MR/CR 流程中测试覆盖率卡点:日增量代码覆盖率、增改代码行覆盖率双指标。不达标不允许合并;

  • CI 阶段自动执行单测并回写覆盖率(JaCoCo/PipeCov),失败即阻断流水线,保证"卡点"不是一句口号。

存量重构(渐进治理):

  • 按风险优先级排序:先给资金、交易这类核心链路补测试,再补长尾;

  • 结合重构任务补测试,不做无意义的全量补数;

  • 用覆盖率数据可视化各模块现状,形成团队看板与赛马机制。

五、应用结果 ​

该团队推行测试先行的效果:

  • 该团队单测覆盖率从初期的 0.10% 提升到 52.29%,MR 阶段实现代码合并约束;

  • 上线缺陷数显著下降,多处边界缺陷(币种校验、后缀校验等)在单测阶段被拦截;

  • 新人上手周期缩短,重构与升级(如中间件替换)有测试兜底,敢动、动得快。

一句话:测试先行的本质,是把质量左移到写代码的人手里。

六、总结 ​

  • 复杂系统的质量不能靠"人肉 + 上线祈祷",要让测试先行。编码阶段用单测验证逻辑,MR 阶段用覆盖率卡点拦劣化;

  • 方法论 = 提炼单测点(入口/私有方法/DAO)+ 规范(AIR/BCDE)+ 推进策略(增量卡点、存量渐进);

  • 覆盖率不是目的而是手段:守住新增代码的质量底线,再逐步偿还存量技术债。

内容来源:原文 · 2026-10 整理回填。