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过滤。下图展示了这一完整路径。

矩阵策略展开与过滤流程 strategy.matrix os, runtime, deps 维度 笛卡尔积展开 N x M x L 组合 include / exclude 过滤 job: ubuntu-node18-v1 os:ubuntu runtime:18 deps:v1 job: ubuntu-node20-v1 os:ubuntu runtime:20 deps:v1 job: windows-node18-v1 os:windows runtime:18 deps:v1 job: ubuntu-node18-v2 os:ubuntu runtime:18 deps:v2 job: ubuntu-node20-v2 os:ubuntu runtime:20 deps:v2 job: macos-node18-v1 os:macos runtime:18 deps:v1 exclude 移除 macos-node18-v1 ,include 追加 linux-node19-v3 最终执行 5 个任务,而非原始笛卡尔积 9 个

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 确保一个任务失败不会取消其他仍在执行中的任务,适合后台测试这类需要完整结果覆盖的场景。