持续集成(CI)中的单元测试:Jenkins/GitHub Actions配置自动测试流水线
说实话,很多团队写单元测试写得挺认真,但最后都烂在「没人跑」这三个字上。我见过不止一个项目,测试用例写了一两千条,结果大家只在提测前手动点一下——那跟没写有什么区别?
单元测试要想真正发挥作用,必须绑定到持续集成流水线里。每次提交代码,自动拉代码、自动编译、自动跑测试、自动出报告。谁破坏了测试,谁就得负责修。这才是正经玩法。
这一章,我就带你搭两条流水线:一条用 Jenkins,一条用 GitHub Actions。你根据团队情况选一个就行。
为什么非要把测试塞进 CI?
我刚开始带项目时,也犯过懒。觉得测试嘛,开发自己跑一遍就行了。结果呢?
- 有人忘了跑测试就合代码
- 有人跑了一半发现报错,直接跳过
- 有人改了底层接口,上游测试全挂,但没人知道
直到一次上线前,集成测试炸了整整两天。从那以后,我立了个规矩:测试不通过,代码不许合。这个规矩,就是靠 CI 流水线来执行的。
方案一:Jenkins 流水线
Jenkins 是老牌 CI 工具,适合自建服务器的团队。我自己的经验是:别用自由风格项目,直接用 Pipeline(流水线)。Pipeline 用 Jenkinsfile 定义,代码化,能版本管理。
Jenkinsfile 示例
下面是一个 C 语言项目的典型 Jenkinsfile。它做了四件事:拉代码、编译、跑测试、生成报告。
pipeline {
agent any
tools {
// 假设 Jenkins 上配了 GCC 工具链
gcc 'GCC-11'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mkdir -p build && cd build && cmake .. && make -j$(nproc)'
}
}
stage('Unit Test') {
steps {
sh '''
cd build
ctest --output-on-failure -j4
'''
}
post {
always {
// 收集测试结果,Jenkins 会生成趋势图
junit 'build/test-results/*.xml'
}
}
}
stage('Coverage') {
steps {
sh '''
cd build
lcov --capture --directory . --output-file coverage.info
lcov --remove coverage.info '/usr/*' --output-file coverage.info
genhtml coverage.info --output-directory coverage_report
'''
}
post {
always {
// 发布覆盖率报告
publishHTML(target: [
reportName: 'Coverage Report',
reportDir: 'build/coverage_report',
reportFiles: 'index.html'
])
}
}
}
}
post {
failure {
// 测试失败时发邮件通知
mail to: 'team@example.com',
subject: "Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "请检查构建结果:${env.BUILD_URL}"
}
}
}
post 块里,我通常还会加一个 unstable 状态处理。有些测试是「非关键」的(比如性能测试),失败了不拦门禁,但发警告。Jenkins 里用 junit 步骤时,测试失败会让构建标记为 UNSTABLE,而不是 FAILURE。
Jenkins 配置要点
| 配置项 | 说明 | 我的建议 |
|---|---|---|
| 触发器 | 什么时候触发构建 | 用 webhook + Poll SCM 双保险。webhook 实时触发,Poll SCM 兜底 |
| 测试报告 | JUnit 格式的 XML | 确保测试框架输出 JUnit 格式。CUnit、Check、Unity 都支持 |
| 超时设置 | 防止测试卡死 | 我一般设 30 分钟超时。超过就自动杀掉,标记为失败 |
| 并行执行 | 多机并行跑测试 | 如果测试用例多,用 parallel 分片跑。我分过 8 片,半小时缩到 5 分钟 |
agent { docker 'gcc:11' } 就行。
方案二:GitHub Actions 流水线
如果你的代码托管在 GitHub 上,GitHub Actions 是最省事的选择。不用自己搭服务器,配置也简单。我个人觉得,小团队和开源项目用 Actions 更合适。
GitHub Actions 工作流示例
在仓库根目录创建 .github/workflows/test.yml:
name: C Unit Test CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake gcc lcov
- name: Configure and Build
run: |
mkdir -p build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON
make -j$(nproc)
- name: Run unit tests
run: |
cd build
ctest --output-on-failure -j4
- name: Generate coverage report
run: |
cd build
lcov --capture --directory . --output-file coverage.info
lcov --remove coverage.info '/usr/*' --output-file coverage.info
genhtml coverage.info --output-directory coverage_report
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./build/coverage.info
fail_ci_if_error: true
- name: Upload test results
uses: actions/upload-artifact@v3
with:
name: test-results
path: build/coverage_report/
matrix 策略很好用。你可以一次跑多个编译器版本、多个平台。比如 matrix: { compiler: [gcc, clang], os: [ubuntu-latest, macos-latest] }。我常用这个来确保代码在不同环境下都能通过测试。
GitHub Actions 配置要点
| 配置项 | 说明 | 我的建议 |
|---|---|---|
| 触发条件 | on push / on pull_request | PR 触发是必须的。push 到 main 也建议触发,防止直接合代码 |
| 缓存 | 加速构建 | 用 actions/cache 缓存 build 目录和依赖。我见过从 5 分钟降到 40 秒的 |
| 测试矩阵 | 多配置并行 | 至少跑 Debug 和 Release 两种配置。Debug 开断言,Release 测性能 |
| 状态徽章 | README 里显示构建状态 | 加上!开源项目靠这个吸引贡献者。闭源项目也方便团队快速了解主干状态 |
两种方案的对比与选择
你可能会问:到底选 Jenkins 还是 GitHub Actions?我个人的经验是这样的:
- 选 Jenkins:你们有专门的运维团队,代码托管在自建 Git 服务器,或者需要高度定制化的流水线(比如要对接内部审批系统、要跑在特定硬件上)。
- 选 GitHub Actions:代码在 GitHub 上,团队不大,不想折腾服务器。说白了,大部分中小团队选 Actions 就够了。
嗯,这里要注意一点:不要两个都上。我见过一个团队,Jenkins 和 Actions 同时跑,结果两边配置不一致,同一个测试一边过一边挂,排查起来特别痛苦。选一个,用到底。
CI 流水线的核心逻辑
下面这张图,是我自己总结的 CI 测试流水线核心流程。每次提交代码,都会走一遍这个循环:
说白了,这个流程的核心就一句话:自动化、门禁化、可追溯。自动化是机器跑,门禁化是不通过就拦,可追溯是每次构建都有日志和报告。
常见问题与避坑
最后,分享几个我实际踩过的坑:
- 测试环境不一致:开发机是 Ubuntu 22.04,CI 节点是 CentOS 7,链接了不同的库版本。解决方案——用 Docker 统一环境。
- 测试用例太慢:一开始把所有测试都串行跑,一次构建要 40 分钟。后来分了 4 片并行,降到 12 分钟。再后来只跑「受影响」的测试,降到 3 分钟。
- 误报太多:有些测试不稳定,偶尔过偶尔挂。我专门建了一个「不稳定测试」列表,先隔离排查,不拦门禁。
- 报告没人看:测试报告生成了,但没人点开看。后来我在 PR 评论里自动贴摘要——通过率、覆盖率变化、失败用例列表。大家才真正开始关注。
好了,这一章的内容就到这里。Jenkins 和 GitHub Actions 的配置示例都在上面了,你直接复制改改就能用。如果你们团队还在用手动跑测试,赶紧搭一条流水线——相信我,你会爱上那种「提交代码后自动告诉你结果」的感觉。