软件项目开发全流程 · 第 15 课 · 持续集成(CI)
1、"集成地狱":一个经典的团队噩梦
一个五人团队,各自在自己的分支上开发了近一个月,谁都没合过。到"集成日"才发现:
- 小王和小李都"新增"了
UserService,重名冲突。 - 小张升级了依赖 A,和主力升级的依赖 B 版本冲突,编译不过。
- 每个人的代码单看都能跑,合到一起一下冒出 200 个错误。
这就是 集成地狱(Integration Hell):把"集成"攒到最后一次性做,等于把几十个问题堆在一起爆发。
持续集成(Continuous Integration,CI)的核心思想只有一句话:
频繁地把代码合回主干,每次合入都自动构建 + 自动测试。
把"一个月集成一次"变成"一天集成多次",让问题小步暴露、及时修复。
2、CI 的三条实践原则
CI 不只是"跑个流水线",它是一套纪律:
- 频繁提交到主干:至少每天一次,越频繁冲突越小(呼应第 12 课"提交要小步、原子")。
- 每次提交都自动验证:构建 + 测试 + 检查,全自动,不靠人记得跑。
- 坏了立即修,最高优先级:CI 变红就是全队最高优先级事件,不许"先这样待会儿再修"。
第三条常被忽视,却最关键。一条长期红的流水线等于没有流水线——没人再信它,它就死了。
3、流水线(Pipeline):CI 里跑什么
一次 CI 运行通常按顺序跑这些阶段("流水线"名字的由来):
① 检出代码 git clone / checkout
↓
② 装依赖 恢复缓存 or 安装
↓
③ 静态检查 格式化检查 + Lint + 类型检查 (第 14 课)
↓
④ 构建 编译 / 打包
↓
⑤ 测试 单元测试 → 集成测试 (第 15、19 课)
↓
⑥ 产出/报告 测试报告、覆盖率、构建产物关键设计:快速反馈优先。
- 越早的阶段越快:静态检查几秒钟,就该排在跑几分钟的测试前面。
- 失败即停:静态检查挂了就别编译了,别浪费时间。
- 并行:单元测试可以和集成测试并行跑(如果资源允许)。
一个阶段耗时参考(好的体验):
| 阶段 | 目标耗时 |
|---|---|
| 静态检查 | < 30 秒 |
| 单元测试 | < 2 分钟 |
| 整体 PR 流水线 | < 10 分钟 |
CI 超过 10 分钟,人类的注意力就散了。 开发者去干别的、忘了这条流水线,反馈延迟就失去了意义。优化 CI 时长是一等一的任务(缓存依赖、并行测试、只测改动的部分)。
4、CI 的落地:工具与配置文件
主流 CI 工具:GitHub Actions(最流行)、GitLab CI、Jenkins、CircleCI 等。
关键理念:CI 配置是代码,进版本库(infrastructure as code)——它和源码一起评审、一起演进。
一个 GitHub Actions 示例(.github/workflows/ci.yml):
name: CI
on:
pull_request: # PR 时触发
push:
branches: [main] # 合入主干时也触发
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip' # 缓存依赖,加速
- name: 安装依赖
run: pip install -r requirements.txt
- name: 静态检查
run: ruff check . && ruff format --check .
- name: 类型检查
run: mypy src/
- name: 运行测试
run: pytest --cov=src --cov-report=term逐段理解:
on:—— 触发条件。PR 和推送到 main 时触发("每次提交都验证"的落地)。cache: 'pip'—— 依赖缓存,避免每次重装依赖,这是缩短 CI 时长最有效的手段之一。- 各 step —— 就是第 14 课的静态检查和测试,串成自动化流水线。
--cov—— 顺带产出覆盖率报告。
结果:这个配置一旦进仓库,每次有人提 PR,GitHub 就自动跑这一整套,结果贴在 PR 上。绿色通过 / 红色失败,一目了然。
5、门禁(Branch Protection):让 CI 真正"卡住"人
CI 跑得再全,如果红了也能合并,就形同虚设。所以要结合第 13 课的分支保护:
PR 提交
↓
CI 自动运行
↓
├── ❌ 失败 → 「合并」按钮被禁用,无法合入 main
│
└── ✅ 通过 + 至少 1 人批准 → 允许合并在 GitHub 上配置 main 的 branch protection:
- ✅ Require status checks to pass(CI 必须绿)
- ✅ Require pull request reviews(至少 1 人批准)
- ✅ Require branches to be up to date(分支要先跟上主干)
这才是完整的闭环:规范(约定)→ 自动化(CI)→ 门禁(强制)。人可以不自觉,机制不会。
6、CI 之外:持续交付与持续部署
CI 是"持续"家族的第一步,后面还有:
| 术语 | 含义 | 关键差别 |
|---|---|---|
| CI 持续集成 | 每次提交自动构建+测试 | 保证代码随时可集成 |
| CD 持续交付(Delivery) | CI 通过后自动部署到测试环境,人工点一下才能上生产 | 软件随时可发布 |
| CD 持续部署(Deployment) | 通过后全自动上生产 | 无人工干预,风险更高 |
写代码 → 提交 → CI(构建+测试) → 部署到预发 → 人工审批 → 上生产
└─持续集成─┘ └──────持续交付──────┘
(持续部署则去掉"人工审批")持续部署的前提:
- 完善自动化测试(不然自动上线等于自动出事故)
- 灰度发布 / 特性开关 / 蓝绿部署(先小流量验证,出问题能秒回滚)
一步步来:先把 CI 做扎实(本课),再谈 CD。没有 CI 的 CD 是灾难。
7、常见坑
| 坑 | 后果 | 应对 |
|---|---|---|
| 流水线太慢(>15 分钟) | 没人等,失去反馈意义 | 缓存依赖、并行测试、只测改动 |
| 随机失败的测试(flaky) | 团队开始"重跑一下就好",不再信 CI | flaky 测试必须当天修或删,不许留 |
| 流水线红了没人管 | CI 形同虚设 | 红了全队最高优先级,或自动回滚 |
| 在 CI 里用真实生产密钥 | 泄露/误操作 | 用 CI 的密钥管理(secrets),只读权限 |
| CI 配置散落、不评审 | 没人懂怎么改 | .yml 进版本库,走 PR 评审 |
flaky 测试是 CI 的慢性毒药:一个测试"有时候过有时候不过",团队会养成"重新跑一次"的坏习惯,然后真 Bug 也被当成 flaky 忽略掉。见到 flaky,立刻修或隔离,不许放养。
8、动手实验 🎯
任务:给你自己的 GitHub 项目配一条完整 CI。
- 创建
.github/workflows/ci.yml,实现:安装依赖(开缓存)→ 静态检查 → 测试。 - 故意提交一个会失败的测试,观察 CI 变红、PR 上出现红叉。
- 修好它,观察变绿;确认整个过程无需手动跑任何命令。
- 配置
main的分支保护:要求 CI 通过 + 至少 1 人批准。然后试试直接往 main push,验证被拦住。 - 优化速度:记录优化前后的 CI 耗时,至少用一招(开依赖缓存 / 只跑改动相关测试 / 并行)。
- 写一段 200 字的 CI 使用约定:流水线红了怎么办、多久必须响应、flaky 测试怎么处理。
- (挑战)思考你项目的"持续交付"还差什么:缺自动化部署?缺回滚?缺灰度?列出来。
做完这步,团队就有了自动质量守门人——下一课把视角拉高,讲敏捷实践怎么把这些工程实践串成一套工作方式。
9、今天你带走的三句话
- CI 的核心是"频繁集成 + 自动验证",把"一个月集成一次"变成"一天多次",消灭集成地狱。
- 流水线要快(<10 分钟)、红了要立刻修;配上门禁,把规范从"口头约定"变成"机制强制"。
- fiaky 测试是慢性毒药,见到就修或删;先做扎实 CI,再谈持续交付/部署。
下一课讲敏捷开发实践——把前面这些工程手段放进一套迭代、协作的工作方式里。