4. 矩阵策略构建测试矩阵:strategy.matrix 定义操作系统、运行时、依赖版本,避免冗余任务
当后台任务需要在多种操作系统、运行时版本或依赖组合上验证时,如果为每个组合手动编写单独的工作流,将导致配置膨胀和维护噩梦。GitHub Actions 的 strategy.matrix 允许你通过一次定义自动展开所有组合,同时利用 include 和 exclude 精确控制任务集,从根本上消除冗余。
4.1 矩阵策略的核心产物
矩阵策略的核心是一个笛卡尔积展开器。你只需在 strategy.matrix 下声明多个维度(例如 os、runtime、deps),Actions 引擎会生成所有维度的乘积作为独立作业。每个作业都继承相同的 steps 结构,仅通过 ${{ matrix.xxx }} 引用当前组合的变量值。
4.2 矩阵定义的基本结构与参数
| 参数 | 作用 | 示例 |
|---|---|---|
os |
操作系统列表,决定运行器标签 | ["ubuntu-latest", "windows-latest", "macos-latest"] |
runtime |
运行时或语言版本 | ["node:18", "node:20", "node:22"] |
deps |
依赖锁文件或索引版本 | ["lock-v1", "lock-v2"] |
include |
在笛卡尔积基础上追加或覆盖特定组合 | include: [{os: "windows", runtime: "node:20"}] |
exclude |
从笛卡尔积中移除已知无效或冗余组合 | exclude: [{os: "macos", runtime: "node:18"}] |
4.3 矩阵展开流程:从定义到任务清单
矩阵策略在 Actions 引擎内部经历三个阶段:维度收集、笛卡尔积展开、include/exclude过滤。下图展示了这一完整路径。
4.4 实践:如何利用 include / exclude 避免冗余任务
假设后台测试需要覆盖三个操作系统、两个 Node.js 版本和两种依赖锁版本,笛卡尔积将有 3×2×2=12 个任务。但实际场景中,有些组合是冗余或无效的:例如 Node 18 不再支持新依赖锁格式,或 macOS 上无需运行旧依赖版本。使用 exclude 精确移除这些组合。
以下配置基于矩阵策略避免冗余:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runtime: [18, 20, 22]
deps: [lock-v1, lock-v2]
exclude:
- runtime: 18
deps: lock-v2
- os: macos-latest
runtime: 18
include:
- os: ubuntu-latest
runtime: 22
deps: lock-v3
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.runtime }}
- name: Install deps
run: |
if [ "${{ matrix.deps }}" == "lock-v1" ]; then
npm ci --prefer-offline
else
npm install
fi
- name: Run tests
run: npm test
4.5 最佳实践:压缩任务清单的实用策略
- 识别无效组合:在
exclude中移除不兼容的运行时与依赖对,例如 Node 16 + 新包管理器。 - 按平台剪枝:如果某操作系统上特定版本的运行时有已知构建失败,直接排除该组合。
- 使用 include 补充特例:当需要为一个特定操作系统添加一个不同于默认版本号的运行时,用
include注入,而不是手动定义新作业。 - 避免全量笛卡尔积:先列出实际需要验证的 5-8 个场景,再用矩阵快速生成,最后修剪到合理数量。
- 检查任务数量:工作流执行前可以在 Actions 标签页预览总任务数,确保不超免费配额。
4.6 调试矩阵:检查已展开与已过滤的任务
Actions 会在工作流运行开始时打印矩阵展开日志。打开对应运行记录,在“Set up job”步骤内可以看到类似以下输出:
Matrix: {
"os": ["ubuntu-latest", "windows-latest", "macos-latest"],
"runtime": ["18", "20", "22"],
"deps": ["lock-v1", "lock-v2"]
}
Excluded: [ {"runtime":"18","deps":"lock-v2"}, {"os":"macos-latest","runtime":"18"} ]
Included: [ {"os":"ubuntu-latest","runtime":"22","deps":"lock-v3"} ]
Total jobs after filtering: 10
如果发现实际任务数量与期望不符,检查 exclude 条件是否拼写错误(注意大小写和空格),或 include 中的键是否与维度完全匹配。
4.7 常见错误:任务冲突与不可见的冗余
include 添加的组合与已有组合的键值完全一致时,Actions 会覆盖原有组合而非创建新组合。这可能导致预期外的缺失任务。避免方法是确保 include 组合至少包含一个唯一维度值。
隐藏冗余示例:如果矩阵中同时存在 os:ubuntu-latest 和 os:ubuntu-22.04,但两个标签指向相同运行器镜像,则属于无意义冗余。统一使用语义标签或限定具体版本。
4.8 性能优化:减少任务数加速流水线
矩阵策略的每个任务都需要分配运行器、拉取代码、缓存依赖。任务数越多,整体排队和执行时间线性增长。通过以下方式优化:
- 限制并行度:使用
max-parallel: 3避免同时消耗过多运行器配额。 - 分阶段矩阵:将慢速的依赖版本测试放在独立作业矩阵,与快速测试矩阵分离,减少阻塞。
- 缓存共享:为不同
deps版本设计独立的缓存 key,避免每次从头安装。
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runtime: [18, 20]
deps: [lock-v1, lock-v2]
max-parallel: 4
fail-fast: false
fail-fast: false 确保一个任务失败不会取消其他仍在执行中的任务,适合后台测试这类需要完整结果覆盖的场景。