GitHub Actions Matrix 矩阵构建:多版本并行测试实战
上周项目上线,下午还没过就收到用户反馈:Node 16 环境下页面直接白屏。我当时脑子里嗡一下。
排查了整整两小时。日志翻了一遍,代码对比了三轮,最后发现是某个 API 在 Node 16 下处理 JSON.stringify() 的行为跟 Node 20 不一样——旧版本遇到循环引用直接抛错,新版本会静默处理。CI 流水线只测了 Node 20,这个兼容性问题压根没被拦截。
事后复盘的时候我就在想:如果当初配置了多版本并行测试,这些问题上线前就能被发现。也就是那次之后,我开始认真研究 GitHub Actions 的 Matrix 矩阵构建——这个词听起来挺唬人,说白了就是把一个任务自动拆成多份,同时跑在不同版本、不同平台上。
这篇文章我会把 Matrix 从基础到进阶完整讲一遍。包括基本语法、exclude/include 过滤组合、fail-fast 策略怎么选、max-parallel 资源控制。最后给你一个可以直接复制使用的完整 Node.js 多版本测试模板。大概 10 分钟看完,回去就能把 CI 改成多版本并行跑。
Matrix 基础 — 5 分钟上手
Matrix 一句话就能理解:你写一个 job,GitHub Actions 自动帮你展开成多个并行任务。
举个例子。你定义三个 Node.js 版本 [18, 20, 22],Matrix 就会创建三个独立的测试任务,分别在 Node 18、Node 20、Node 22 环境下跑。这三个任务同时启动,同时执行,互不干扰。
最简配置是这样的:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci && npm test
重点看 strategy.matrix 这一块。node-version 是你自己定义的变量名,后面的数组 [18, 20, 22] 就是这个变量能取的值。GitHub Actions 会遍历这个数组,每次取一个值赋给 matrix.node-version,然后创建一个对应的 job 实例。
${{ matrix.node-version }} 这个语法就是在引用当前的值。第一次执行时它是 18,第二次是 20,第三次是 22。
我第一次用的时候有个疑问:这三个任务是串行还是并行?答案是默认并行。你推一次代码,GitHub 会同时启动三个 Runner,三个任务一起跑。实测下来,之前串行测三个版本要 15 分钟,改成 Matrix 后只要 5 分钟——因为三个任务同时在跑,时间就压缩到最慢那个任务的耗时。
不过有一点要注意:并行跑意味着消耗更多 Runner 分钟数。三个任务就是三倍的时间消耗。如果你用的是免费额度(每月 2000 分钟),大矩阵可能会很快把额度吃光。这个后面讲 max-parallel 的时候会细说。
exclude/include 过滤 — 细控制测试矩阵
当你开始组合多个维度的时候,Matrix 的组合数会指数级增长。
比如三个 Node 版本 [16, 18, 20],三个操作系统 [ubuntu, windows, macos],组合起来就是 3 x 3 = 9 个任务。再加上测试套件 [unit, integration, e2e],就是 27 个。你说这算不算多?对于开源项目可能还好,但对于小团队,Runner 分钟数就是成本。
而且有些组合本身就没意义。Node 16 已经 EOL(End of Life)了,在 Windows 和 macOS 上测它纯属浪费时间。这个时候就要用 exclude 把这些组合剔除。
strategy:
matrix:
node-version: [16, 18, 20]
os: [ubuntu-latest, windows-latest, macos-latest]
exclude:
- node-version: 16
os: windows-latest
- node-version: 16
os: macos-latest
exclude 的写法就是把要排除的组合列出来。上面的配置会删除 Node 16 + Windows 和 Node 16 + macOS 这两个组合。原本 9 个任务变成 7 个,直接省了 22% 的 Runner 分钟。
include 则是反向操作——添加特定的组合或额外变量。举个例子,你想加一个 Node 23 的实验版本测试,但只跑 Ubuntu:
strategy:
matrix:
node-version: [16, 18, 20]
os: [ubuntu-latest, windows-latest]
include:
- node-version: 23
os: ubuntu-latest
experimental: true
这里有个细节:include 不只是添加组合,还能给特定组合加额外变量。上面的 experimental: true 就只会在 Node 23 这个任务里存在。你可以在后续步骤里判断这个变量,比如实验版本失败了不阻止整个 workflow:
- name: Run tests
run: npm test
continue-on-error: ${{ matrix.experimental == true }}
我踩过一个坑:exclude 和 include 的优先级。GitHub Actions 先执行 include 添加组合,再执行 exclude 删除。所以如果你 include 了一个组合,又在 exclude 里排除了它,这个组合是不会出现的。顺序搞反的话,结果可能跟你预想的不一样。
总结下这两个的用法:
exclude:删掉没必要的组合,省钱省时间include:补充特殊组合,还能加额外变量做差异化处理
fail-fast 与 max-parallel — 并行策略调优
Matrix 默认有一个行为你可能没注意到:fail-fast: true。
啥意思?就是矩阵里任何一个任务失败了,GitHub Actions 会立即取消其他还在跑的任务。比如你 10 个任务并行,第 3 个任务跑了一分钟失败了,剩下 7 个任务会被立刻终止。
这个行为好不好?看你场景。
PR 检查的时候,fail-fast 是好事。有人提交代码,发现 Node 18 测试挂了,你不需要等其他版本跑完——直接反馈给作者,让他赶紧修。省时间,省资源。
但如果是 Nightly 测试或者定期回归,fail-fast 可能就不好了。你想要的是完整的测试报告——到底哪些版本有问题,哪些没问题。如果 Node 18 挂了就停掉其他任务,你根本不知道 Node 20 是不是也有同样的问题。这时候应该设置 fail-fast: false。
strategy:
fail-fast: false
matrix:
node-version: [16, 18, 20]
max-parallel 则是控制同时跑多少个任务。默认是不限制,GitHub 会尽可能同时启动所有任务。但对于大矩阵,比如 30 个组合,你可能不想一次性吃掉所有 Runner 资源。
strategy:
fail-fast: true
max-parallel: 6
matrix:
node-version: [16, 18, 20, 22]
test-suite: [unit, integration, e2e]
上面的配置会限制最多同时跑 6 个任务。30 个组合分批执行,每批 6 个。好处是 Runner 资源可控,不会一次性把额度吃光。坏处是总时间会变长。
我整理了一个简单的决策表,方便你根据场景选择:
| 场景 | fail-fast | max-parallel | 原因 |
|---|---|---|---|
| PR 检查 | true | 不限 | 快速反馈,一个失败就停,省时间 |
| Nightly 测试 | false | 4-6 | 收集完整问题报告,定位所有 bug |
| 大矩阵(>20 组合) | true | 4 | 限制资源消耗,避免额度爆炸 |
| 实验版本测试 | false | 不限 | 实验版本失败不影响整体判断 |
说实话,大部分情况下默认的 fail-fast: true 就够了。只有在你需要完整诊断的时候才改成 false。而 max-parallel 对小矩阵(10 个以内)影响不大,大矩阵才需要认真考虑。
有一点要提醒:max-parallel 只是限制 GitHub Actions 同时启动的任务数,不是限制 Runner 数。如果你用的是 self-hosted runner(自建 Runner),设置太小反而会排队等待,拖慢整体时间。公共 Runner 的话,这个设置才有意义。
完整实战模板 — Node.js 多版本并行测试流水线
前面的内容都是碎片知识点。这一章给你一个完整的、可以直接复制用的配置模板。
这个模板包含:
- 三种 Node 版本(16、18、20)
- 两套测试(unit 和 integration)
- 两套操作系统(Ubuntu 和 Windows)
- 自动缓存加速依赖安装
- 排除 Node 16 的 Windows 测试(EOL 版本)
name: Multi-Version Test Matrix
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
max-parallel: 6
matrix:
node-version: [16, 18, 20]
test-suite: [unit, integration]
os: [ubuntu-latest, windows-latest]
exclude:
- node-version: 16
os: windows-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ${{ matrix.test-suite }} tests
run: npm run test:${{ matrix.test-suite }}
重点说几个地方:
runs-on: ${{ matrix.os }}:操作系统也是动态的,每个任务会根据矩阵组合选择对应的 Runner。
cache: 'npm':这是 setup-node 自带的缓存功能。它会根据 package-lock.json 的 hash 缓存 npm 依赖,第二次跑的时候直接复用,不用重新下载。实测下来,这个缓存能减少 50% 以上的依赖安装时间。
fail-fast: false:这里故意设置成 false,因为多版本测试的目的就是发现所有问题。一个版本挂了,其他版本还要继续跑完。
npm run test:${{ matrix.test-suite }}:假设你的 package.json 里定义了 test:unit 和 test:integration 两个命令,Matrix 会分别调用。
这个配置总共会产生多少个任务?
3 个版本 x 2 个测试 x 2 个操作系统 = 12 个任务,减去排除的 Node 16 + Windows(2 个测试套件),剩下 10 个任务。
实测数据:我用这个配置跑了几个项目,配合缓存后,CI 时间从之前串行的 25 分钟压缩到 8 分钟左右。时间主要省在并行执行和依赖缓存两个地方。
如果你项目更大,组合更多,可以考虑:
- 增加
max-parallel上限(比如 8 或 10) - 把 e2e 测试单独拆一个 job,避免拖慢整体
- 用
continue-on-error处理实验版本的失败
把这个模板复制到你的 .github/workflows/test.yml,根据实际情况调整版本号和测试套件名,应该就能跑起来了。
结论
说了这么多,总结下核心要点:
Matrix 本质上就是把一个 job 自动展开成多个并行任务。配置简单,效果直接——一次推送,三个版本同时跑,CI 时间直接压缩到最慢那个任务的耗时。
exclude/include 是精细控制的手段。组合太多的时候用 exclude 删掉没必要的任务,能直接省掉 20%+ 的 Runner 分钟。include 则是补充特殊组合,还能加额外变量做差异化处理。
fail-fast 默认 true,一个失败就停其他任务。PR 检查用默认就好,Nightly 测试改成 false 收完整报告。max-parallel 控制并发上限,大矩阵才需要关注这个设置。
缓存是标配。setup-node 自带的 cache 功能,配置一行代码就能省掉一半依赖安装时间。
下一步建议:把上面的完整模板复制到你的项目 .github/workflows/ 目录下,先跑 Node.js 三版本测试。确认没问题之后,再逐步扩展到多平台和多测试套件。如果遇到缓存配置的问题,可以看看系列文章《GitHub Actions 缓存策略:加速 CI/CD 流水线 5 倍》,里面有更细的技巧。
多版本测试这事儿,越早配置越好。别等到上线出问题才后悔——那个白屏 bug,我现在想起来还头疼。
配置 GitHub Actions Matrix 多版本测试
从零搭建多版本并行测试流水线,覆盖 Node.js 16/18/20 版本和 Ubuntu/Windows 平台
⏱️ 预计耗时: 15 分钟
- 1
步骤1: 创建 Workflow 文件
在项目根目录创建 `.github/workflows/test.yml` 文件:
• 确保目录结构正确:`.github/workflows/`
• 文件名自定义,建议用 `test.yml` 或 `ci.yml` - 2
步骤2: 配置触发条件
定义何时触发测试:
```yaml
on:
push:
branches: [main]
pull_request:
```
• main 分支推送触发
• PR 创建或更新触发 - 3
步骤3: 定义 Matrix 矩阵
配置版本、平台和测试套件:
```yaml
strategy:
fail-fast: false
max-parallel: 6
matrix:
node-version: [16, 18, 20]
test-suite: [unit, integration]
os: [ubuntu-latest, windows-latest]
exclude:
- node-version: 16
os: windows-latest
```
• fail-fast: false 收集完整测试结果
• max-parallel: 6 控制并发上限
• exclude 排除无效组合 - 4
步骤4: 配置测试步骤
定义具体的测试执行步骤:
```yaml
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run test:${{ matrix.test-suite }}
```
• cache: 'npm' 启用依赖缓存
• 使用 matrix 变量动态配置版本 - 5
步骤5: 提交并验证
推送代码触发测试:
• 提交到 main 分支或创建 PR
• 在 GitHub Actions 页面查看并行任务执行情况
• 检查各版本测试结果是否正常
常见问题
Matrix 矩阵构建会消耗更多 Runner 分钟数吗?
fail-fast 应该设置 true 还是 false?
• PR 检查:建议 true(快速失败,立即反馈)
• Nightly 测试:建议 false(收集完整报告)
• 实验版本:建议 false(避免影响整体判断)
默认是 true,大部分 PR 场景够用。
exclude 和 include 谁先执行?
max-parallel 设置多少合适?
• 小矩阵(<10 组合):不需要设置,使用默认值
• 中矩阵(10-20 组合):设置为 6-8
• 大矩阵(>20 组合):设置为 4-6
设置过小会延长总等待时间,设置过大可能一次性耗尽 Runner 资源。
如何在 Matrix 中使用缓存加速?
Matrix 支持哪些变量类型?
• 数组:`[18, 20, 22]`
• 对象数组:`[{name: 'a', value: 1}, {name: 'b', value: 2}]`
• 字符串:需要用 include 添加
推荐使用数组和对象数组,可读性更好。
11 分钟阅读 · 发布于: 2026年4月8日 · 修改于: 2026年4月8日
相关文章
Supabase Auth 实战:邮箱验证、OAuth 与会话管理
Supabase Auth 实战:邮箱验证、OAuth 与会话管理
GitHub Actions 部署策略:从 VPS 到云平台的 CD 流水线
GitHub Actions 部署策略:从 VPS 到云平台的 CD 流水线
GitHub Actions 缓存策略:加速 CI/CD 流水线 5 倍

评论
使用 GitHub 账号登录后即可评论