
最近折腾了一下 CI/CD 测试管理,把 GitHub Actions、GitLab CI 和 Jenkins 三套流水线都跑了一遍,顺手做个横向对比。这三个工具的选型文章一搜一大把,但大多是在对比功能列表,配置文件的写法却很少有人放到一起对照着看。这篇不一样——我用同一套测试用例(Jest + Supertest,8 个接口测试,100% 覆盖率),分别写三份配置文件跑一遍,然后把真实配置贴出来,有什么优缺点自己看。
完整的 demo 仓库(包含三份配置文件):https://github.com/GianfrancoArocutipa/ci-testing-comparative
要对比就得公平。三个工具各干同样的事情:
npm ci 安装依赖npx jest --coverage --ci(8 个接口测试,针对 Express 应用)三个配置文件同时存在一个仓库里,这也是实际场景中常见的做法——团队在迁移 CI 工具时,经常会并行跑一段时间做对照。
.github/workflows/ci.yml → GitHub Actions
.gitlab-ci.yml → GitLab CI/CD
Jenkinsfile → Jenkins
配置文件放在 .github/workflows/ 目录下,一个 YAML 文件定义一个 workflow,通过事件触发(push、pull request、定时、手动触发):
name: CI — API Tests on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest # GitHub-hosted runner, zero setup strategy: matrix: node-version: [20, 22] # same suite on two Node versions steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: npm # dependency cache in one line - run: npm ci - run: npx jest --coverage --ci - uses: actions/upload-artifact@v4 if: always() with: name: coverage-node-${{ matrix.node-version }} path: coverage/
最亮眼的是 Marketplace。像 actions/checkout、actions/setup-node 这些都是现成的构建块,npm 依赖缓存只需要一行 cache: npm,不用自己折腾。矩阵策略也很优雅,两行配置就能让测试在 Node 20 和 22 上并行跑。Runner 由 GitHub 提供托管,公有仓库有慷慨的免费额度,失败的检查项会直接阻止 pull request 合并。
配置文件在仓库根目录只有一个 .gitlab-ci.yml,围绕 stages 组织,Docker-first 理念——每个 job 都声明自己要跑在哪个容器镜像里:
image: node:22 # every job runs inside this Docker image stages: - test cache: # cache keyed to the lockfile key: files: - package-lock.json paths: - .npm/ api-tests: stage: test script: - npm ci --cache .npm --prefer-offline - npx jest --coverage --ci coverage: '/All files[^|]*\|\s*([\d\.]+)/' # GitLab parses coverage % from the log artifacts: when: always paths: - coverage/ expire_in: 1 week
最亮眼的是 coverage: 关键字——直接写个正则表达式就能从日志里提取覆盖率百分比,显示在每个 Merge Request 页面,不需要装任何插件。Artifact 有原生的过期策略。Docker-first 的模式让环境一致性很有保障,"跑在 node:22 里" 就说清楚了所有依赖。代价是缓存配置比 GitHub 的一行配置更手动一些,可复用组件库也没有 GitHub Marketplace 那么丰富。
老前辈了。配置文件是 Groovy 语法的 Jenkinsfile,最根本的区别在于托管模式——服务器要自己搭。
pipeline { agent { docker { image 'node:22' } // requires Docker on the Jenkins node } stages { stage('Install') { steps { sh 'npm ci' } } stage('Test') { steps { sh 'npx jest --coverage --ci' } } } post { always { archiveArtifacts artifacts: 'coverage/**', allowEmptyArchive: true } failure { echo 'API contract broken — merge should be blocked.' } }
}
最亮眼的是完全掌控。Jenkins 跑在自己架设的基础设施上——对于银行、医院或者代码不能离开内网的场景,这是硬性要求。插件生态有约 1900 个,基本覆盖所有能想到的集成场景。post 块可以对成功、失败、无论成功失败分别做处理,控制粒度很细。代价是运维成本:自己安装、自己升级、自己做安全加固、自己扩缩容。没有免费托管额度,因为压根就没有托管服务这回事;缓存、PR 阻断、密钥管理这些功能都需要额外配置,而在 GitHub Actions 和 GitLab CI 那边是开箱即用的。
| GitHub Actions | GitLab CI/CD | Jenkins | |
|---|---|---|---|
| 配置方式 | YAML per workflow | 单个 YAML 文件 | Groovy Jenkinsfile |
| 托管模式 | GitHub 托管(或自建) | GitLab 托管(或自建) | 仅支持自建 |
| 上手成本 | 零配置 | 零配置 | 装服务器 + 维护 |
| 依赖缓存 | cache: npm(一行搞定) |
手动写 cache: 块 |
插件 / 手动配置 |
| 测试矩阵 | 原生支持,两行配置 | parallel:matrix |
插件 / 脚本方式 |
| PR/MR 展示覆盖率 | 通过 actions/artifacts | 原生支持 coverage: 正则 |
插件(Cobertura 等) |
| 复用生态 | Marketplace(2 万+ actions) | CI/CD 组件目录 | 约 1900 个插件 |
| 免费额度 | 2000 分钟/月(公有仓库无限) | 400 计算分钟/月 | 免费软件,自备硬件 |
| 适合场景 | 代码已在 GitHub | GitLab 用户群、Docker-first 团队 | 内网部署 / 合规要求严格的环境 |
这个领域的其他工具也遵循类似模式:CircleCI 和 Travis CI 属于托管 YAML 模型,Bitbucket Pipelines 是 Atlassian 体系内的 GitLab 风格,TeamCity 则是带商业化打磨的 Jenkins 路线,Tekton 把 Jenkins 的理念原生搬进了 Kubernetes。
跑完同一套测试之后的真实感受:工具没那么重要,重要的是卡点机制。三个工具都把 8 个测试跑完了,都生成了覆盖率报告,故意改错一个状态码都能正确失败。选哪个是上下文问题,不是能力问题:代码在 GitHub 上,Actions 就是最省事的默认选项,矩阵策略和 Marketplace 在这次实验里确实省了最多代码;公司用 GitLab,CI 深度集成,MR 页面直接显示覆盖率确实舒服;如果业务约束是"代码不能出内网",Jenkins 仍然是标准答案,插件生态依然覆盖了很多托管工具覆盖不到的场景。
这次对比聚焦在测试管理的核心链路(跑测试、缓存、artifact、阻断合并),用的是一个小规模 Node 项目,没有涉及大型 monorepo 的性能表现、CD 部署功能,或者企业级的合规审计需求——那些维度上三个工具的差异会大得多。免费额度和定价也经常变动,选型之前建议去各平台官网确认最新的数字。
同一套测试用例,三套 CI 工具各跑一遍,对比的是真实配置文件,不是功能列表。GitHub Actions 在 ergonomics(人体工学/易用性)上胜出,GitLab CI 在集成报告上占优,Jenkins 在控制力和主权上无可替代。无论选哪个,终点是一样的,也是不可妥协的:每次 push 都自动跑测试,红色构建必须阻断合并。剩下的都是语法问题。