Chapter 04

矩阵构建与并行作业策略

一次配置,在多个操作系统和语言版本上并行运行测试,全面覆盖兼容性

Matrix Strategy 基础

什么是矩阵构建?

矩阵构建(Matrix Build)允许你通过一个 Job 定义,自动生成多个并行运行的 Job 实例,每个实例使用不同的参数组合。例如,在 3 个操作系统 × 3 个 Node.js 版本上测试,只需写一次配置,GitHub Actions 会自动创建 9 个并行 Job。

矩阵构建的核心价值是:不用重复写相似的 Job 配置,且所有组合并行运行,不会增加总构建时间(但会消耗更多 Runner 资源)。

strategy.matrix
定义矩阵维度和各维度的值列表。多个维度之间取笛卡尔积,自动生成所有组合。每个组合生成一个独立的 Job 实例,可通过 ${{ matrix.xxx }} 访问当前实例的值。
include
两种用途:1) 添加笛卡尔积之外的额外组合;2) 为已有组合附加额外属性(精确匹配时只附加属性,不新建组合)。常用于为特定组合配置特殊参数。
exclude
从笛卡尔积中排除特定组合。常用场景:跳过已知有兼容性问题的组合、节省资源(如跳过耗时平台上的旧版本)。
fail-fast
默认 true:任何一个矩阵 Job 失败,立即取消所有其他正在运行的 Job,节省资源;设为 false:所有组合都跑完,收集全部结果,适合发布前的全面兼容性扫描。
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: ['18', '20', '22']
        # 自动生成 3×3 = 9 个 Job 实例

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test
      - name: 在日志中标识当前矩阵
        run: |
          echo "OS: ${{ matrix.os }}"
          echo "Node: ${{ matrix.node-version }}"

高级矩阵配置

include 和 exclude

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    python-version: ['3.10', '3.11', '3.12']

    # include:添加额外的矩阵项或为现有项添加属性
    include:
      # 额外测试 Python 3.9(只在 Ubuntu 上)
      - os: ubuntu-latest
        python-version: '3.9'
        experimental: true
      # 为特定组合添加额外属性
      - os: windows-latest
        python-version: '3.12'
        pip-cache-dir: 'C:\pip-cache'

    # exclude:排除特定组合
    exclude:
      # Windows + Python 3.10 已知有兼容问题,暂时跳过
      - os: windows-latest
        python-version: '3.10'

fail-fast 与 max-parallel

strategy:
  # fail-fast: true(默认)= 任何一个矩阵 Job 失败,取消所有其他 Job
  # fail-fast: false = 所有 Job 都跑完,收集全部结果
  fail-fast: false

  # max-parallel: 限制同时运行的 Job 数量
  # 避免耗尽 Runner 资源或超出 API 速率限制
  max-parallel: 4

  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    node: [18, 20, 22]
何时使用 fail-fast: false?

当你需要了解所有平台的失败情况时(如发布前的全平台兼容性测试),设置 fail-fast: false 更合适。在日常 CI 中,fail-fast: true(默认)更高效——一旦发现失败就立即停止,节省资源。

动态矩阵(从文件生成)

jobs:
  # 第一步:生成矩阵配置
  prepare-matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v4
      - id: set-matrix
        run: |
          # 从 services/ 目录自动发现需要测试的服务
          SERVICES=$(ls services/ | jq -R -s -c 'split("\n")[:-1]')
          echo "matrix={\"service\":$SERVICES}" >> $GITHUB_OUTPUT

  # 第二步:使用动态矩阵
  test-services:
    needs: prepare-matrix
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJSON(needs.prepare-matrix.outputs.matrix) }}
    steps:
      - uses: actions/checkout@v4
      - run: cd services/${{ matrix.service }} && npm test

实战:多语言兼容性测试矩阵

name: Cross-platform Compatibility Test

on: [push, pull_request]

jobs:
  test:
    name: Test (${{ matrix.os }}, Python ${{ matrix.python }})
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-22.04, ubuntu-20.04, windows-2022, macos-14]
        python: ['3.10', '3.11', '3.12']
        include:
          # 在 ubuntu-22.04 + Python 3.12 上额外运行性能基准测试
          - os: ubuntu-22.04
            python: '3.12'
            run-benchmarks: true
        exclude:
          # macOS 上跳过旧版本(节省资源)
          - os: macos-14
            python: '3.10'

    steps:
      - uses: actions/checkout@v4

      - name: 配置 Python ${{ matrix.python }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
          cache: 'pip'

      - name: 安装依赖
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-test.txt

      - name: 运行单元测试
        run: pytest tests/ -v --tb=short

      - name: 运行性能基准测试(仅特定矩阵项)
        if: matrix.run-benchmarks == true
        run: pytest benchmarks/ --benchmark-json=benchmark.json

      - name: 上传基准测试结果
        if: matrix.run-benchmarks == true
        uses: actions/upload-artifact@v4
        with:
          name: benchmark-results
          path: benchmark.json

矩阵构建结果聚合

矩阵 Job 各自独立运行,如何在后续 Job 中聚合所有结果?

jobs:
  # 矩阵测试 Job
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    outputs:
      # 每个矩阵实例都可以设置输出,但后续 Job 收到的是最后一个实例的值
      # 因此单个输出值对矩阵聚合没有意义,通常用 Artifacts 传递结果
      result: ${{ steps.test.outputs.result }}
    steps:
      - uses: actions/checkout@v4
      - id: test
        run: |
          npm test
          echo "result=passed" >> $GITHUB_OUTPUT
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results-${{ matrix.os }}
          path: test-results/

  # 聚合所有矩阵结果的 Job
  aggregate:
    needs: test
    runs-on: ubuntu-latest
    if: always()    # 即使 test Job 失败也执行
    steps:
      # 下载所有矩阵实例的 Artifacts
      - uses: actions/download-artifact@v4
        with:
          path: all-results/

      # 检查 needs.test.result:'success'|'failure'|'cancelled'|'skipped'
      - name: 检查矩阵整体结果
        run: |
          echo "矩阵测试结果: ${{ needs.test.result }}"
          ls -la all-results/

      # 可以合并所有测试报告或发送通知
      - name: 生成汇总报告
        if: needs.test.result == 'failure'
        run: echo "有平台测试失败,详情见各 Artifacts"

并发控制(Concurrency)

矩阵构建会消耗大量并发 Runner。GitHub 免费账户有并发限制(公共仓库无限制,私有仓库根据套餐不同而有所限制)。此外,同一分支的多次推送可能触发多个 Workflow 同时运行,造成资源浪费。

name: CI

on: [push]

# 全局并发控制:同一 group 同时只有一个 Workflow 运行
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true   # 有新推送时取消旧的运行

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      # 全局最多 2 个矩阵 Job 同时运行(避免消耗太多并发额度)
      max-parallel: 2
      matrix:
        node: [18, 20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci && npm test
矩阵构建的成本考量

矩阵构建增加了并发 Runner 的使用量。GitHub Actions 免费额度(公共仓库免费,私有仓库 2000 分钟/月)会被每个矩阵 Job 分别计算。例如 3×3=9 个 Job 每个运行 5 分钟,一次 Workflow 就消耗 45 分钟。对私有仓库,建议在 PR 时只运行关键平台(ubuntu),在合并到 main 时才运行全矩阵。

本章小结

本章核心要点