在 continuous integration 中,何时运行 unit-tests 更合适?
建议分别设置本地开发和 CI 的测试要求。开发时按需运行相关 unit-tests,共享代码前运行完整单元测试。CI 则在每次推送或合并请求更新后自动验证;测试失败时,将该次 CI 构建标记为失败,并阻止合并。
本地编译和启动应用可以保留独立入口,不必每次启动都等待全部测试。团队共享分支应保持测试通过,出现失败后优先修复或回退,不能只发通知就长期搁置。
为什么要区分本地构建和 CI 构建
“编译成功”“应用能够启动”和“测试通过”回答的是不同的问题。构建工具既可以将这些任务串联起来,也可以让它们独立运行。
开发中暂时有测试失败很正常。例如,修改接口时,调用方和测试可能还没同步;采用 TDD 时,也会先写出失败的测试。如果每次运行应用前都必须通过全部测试,就会妨碍调试。
代码进入共享分支后,测试失败会影响其他开发者。他们需要在这个版本上继续工作;如果测试一直失败,就很难判断新问题是否由自己的改动造成。本地可以允许测试暂时失败,CI 则应如实报告验证结果,并通过合并规则控制失败代码何时进入共享分支。
如何安排测试时机
| 时机 | 建议执行的测试 | 失败后的处理 |
|---|---|---|
| 修改代码、调试时 | 与当前改动相关的测试,可使用 IDE 或 watch 模式 | 在当前开发过程中处理 |
| 准备共享改动前 | 完整 unit-tests | 修复后再推送到共享分支;个人草稿分支可另行约定 |
| 推送或合并请求更新后 | CI 自动运行完整 unit-tests | 标记检查失败,阻止合并 |
| 合并进入主分支后 | 验证合并后的版本 | 若失败,优先修复或回退 |
“每次 commit 后运行”通常需要明确为“每次推送后运行”。只在本地创建 commit,远端 CI 通常不会收到事件。一次推送包含多个 commit 时,CI 是否逐个验证,也取决于触发配置。至少应验证准备合并的最新版本。
落地步骤
1. 提供独立的本地运行入口
让开发者可以单独编译、启动应用或运行测试,同时提供一个执行完整检查的入口。
如果现有工具的 build 生命周期已经包含测试,可以保留这个约定,另设开发用的编译或启动命令。命令名称由项目决定,但团队需要清楚每个命令实际执行哪些检查。
2. 在共享代码前完成本地检查
开发时先运行相关测试,缩短反馈时间;准备共享改动时,再运行完整 unit-tests,检查是否影响其他模块。
本地 hook 可以帮助执行检查,CI 仍需独立运行测试。本地环境和执行习惯各有差异,团队需要统一且可查看的验证结果。
3. 让 CI 失败产生明确后果
测试失败应使 CI 任务失败。采用合并请求的团队应把单元测试设为必需检查,只有目标版本通过后才允许合并。
如果多人直接向同一共享分支提交,提交后的 CI 就无法阻止失败代码先进入分支。这种工作方式需要更严格的提交前检查,也需要约定失败后的处理方式:由引入问题的人优先修复;无法及时修复时,回退相关改动,恢复可用基线。
合并检查还应考虑目标分支的变化。各分支分别通过测试,不代表合并后的版本一定通过。可以根据 CI 平台的能力验证合并结果,或要求更新到最新基线后重新检查。
一个任务拆分示例
这个 Makefile 示例假定项目已有对应脚本,且 unit-tests.sh 在测试失败时返回非零退出码。脚本名称需要替换为项目实际命令。
.PHONY: build unit-tests verify run
build:
./scripts/build.sh
unit-tests: build
./scripts/unit-tests.sh
verify: unit-tests
run: build
./scripts/run.sh
本地调试时启动应用:
make run
共享代码前和 CI 中执行完整验证:
make verify
运行应用只依赖编译成功,完整验证则要求编译和单元测试都成功。CI 必须保留命令的失败状态,不能忽略测试错误后继续报告成功。
测试耗时和 integration-tests 怎么处理
如果完整 unit-tests 慢到开发者经常跳过,应先检查时间花在哪里:测试数量、重复初始化、外部服务依赖,还是执行方式。可以优先运行相关测试、隔离测试数据,或并行执行测试。并行执行的前提是测试之间没有共享状态冲突。
对于依赖数据库、网络或其他外部进程的测试,应重新检查分类,明确是否应放入 integration-tests 流程。测试类型不能仅凭耗时长短划分。
已有独立的 integration-tests 流程可以继续保留,但要明确哪些检查结果会阻止合并或发布。unit-tests 通过,只能说明这些单元测试覆盖的行为符合预期,不能替代集成验证。如果某项集成检查决定代码能否合并,就应等待对应版本的检查结果。
偶发失败的测试也要及时处理。确需临时隔离时,应指定负责人和修复期限,并让团队清楚隔离了哪些测试。反复重跑直到变绿,会让失败结果越来越难以判断。
备注:内容仅供参考。
