GitHub Actions cron + 矩阵策略:定时触发多版本测试完整方案

GitHub Actions cron + 矩阵策略封面图

解决什么问题

每天凌晨自动跑「Python 3.10 / 3.11 / 3.12 × Ubuntu / Windows / macOS」共 9 个组合的测试,或者每周一检查依赖有没有安全更新。GitHub Actions schedule 触发器配合矩阵策略,可以用最少的 YAML 实现这些定时任务。但 cron 表达式时区搞错、矩阵组合耗尽配额、依赖版本飘移导致漏报——这三个坑我全踩过,以下是完整排坑方案。

基础配置:cron + 矩阵的最简写法

下面是一个每天 UTC 14:00(北京时间 22:00)自动运行的测试矩阵:

name: Scheduled Multi-Version Test
on:
  schedule:
    - cron: '0 14 * * *'  # 每天 UTC 14:00 = 北京时间 22:00

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ['3.10', '3.11', '3.12']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pip install -r requirements.txt
      - run: pytest tests/

9 个组合,每个组合独立运行,互不影响。GitHub Actions 会自动按字母序分配配额——免费账户每月 2000 分钟,超出后 job 会排队等待,不会直接失败。

踩坑一:cron 时区(最容易出错的地方)

schedule 的 cron 表达式 全部在 UTC 时间运行,不是本地时间。免费账户很难意识到这一点。

我第一次配置时,想设「北京时间每天 22:00 跑」,直接写:

# 错误!这是 UTC 22:00 = 北京时间次日 06:00
- cron: '0 22 * * *'

结果跑出来发现是凌晨 6 点,不是晚上 10 点。正确的换算:

  • 北京时间 22:00 = UTC 14:00(UTC+8)
  • 北京时间 14:00(下午)= UTC 06:00
  • 不要凭感觉写,用 date -u 先验证
# 验证当前 UTC 时间
$ date -u
Sun May 24 06:02:25 UTC 2026

# 验证对应北京时间
$ TZ='Asia/Shanghai' date
Sun May 24 14:02:25 CST 2026

踩坑二:每周只在周一跑,防止配额浪费

如果你的测试不需要每天跑(比如依赖安全检查),可以限定只在周一执行,减少配额消耗:

on:
  schedule:
    # 每周一 UTC 08:00 = 北京时间 16:00
    # 表达式:分(0) 时(8) 日(*) 月(*) 周一(1)
    - cron: '0 8 * * 1'

如果矩阵组合超过 10 个,建议加上 fail-fast: false,防止一个组合失败时 GitHub 取消其他正在运行的 job:

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    python-version: ['3.10', '3.11', '3.12']
  fail-fast: false  # 一个失败不取消其他

踩坑三:依赖更新漏报——用 pip-compile 生成锁定版本

定时任务跑的是当前环境的最新依赖,但如果 requirements.txt 没有锁定版本,测试结果不稳定(今天 pass、明天 fail),无法定位是代码问题还是依赖问题。

正确做法是用 pip-compile 生成锁文件:

# requirements.in(只写包名,不写版本)
requests>=2.28
pytest>=7.0
httpx>=0.24

# 生成 requirements.txt(锁定版本)
$ pip install pip-tools
$ pip-compile requirements.in --generate-hashes

# requirements.txt 会被锁定到精确版本
# requests==2.31.0 --hash=sha256:...
# pytest==7.4.0 --hash=sha256:...
# httpx==0.25.0 --hash=sha256:...

在 CI 中验证锁文件和实际安装的版本一致:

- name: Verify pinned dependencies
  run: |
    pip install -r requirements.txt
    # 导出实际安装的版本,对比 lock 文件
    pip freeze | diff - requirements.txt || exit 1

踩坑四:矩阵组合过多导致配额提前耗尽

3 个 OS × 3 个 Python 版本 = 9 个组合,如果每个跑 5 分钟,就是 45 分钟。如果每天跑,一个月下来接近 1350 分钟——接近免费账户 2000 分钟的配额上限。

一个实际解决思路:不要测所有组合,用「锚点组合」覆盖主要风险:

# 只测关键组合,不是全矩阵
# 生产环境用 Ubuntu + 最新 Python,用这个做冒烟测试
# 其他 OS/Python 版本只在 release tag 时跑完整矩阵
strategy:
  matrix:
    include:
      # 冒烟测试(每天跑)
      - os: ubuntu-latest
        python-version: '3.12'
      # 回归测试(release 时跑)
      - os: ubuntu-latest
        python-version: ['3.10', '3.11', '3.12']
      - os: windows-latest
        python-version: ['3.11', '3.12']
      - os: macos-latest
        python-version: ['3.11', '3.12']

on.schedule 配合 on.push tags 做双触发:

on:
  schedule:
    - cron: '0 8 * * 1'  # 每周一 UTC 8:00 冒烟测试
  push:
    tags:
      - 'v*'  # release tag 时跑完整测试

踩坑五:Windows 路径中的换行符导致缓存失效

在矩阵策略下使用 actions/cache 时,Windows runner 的路径分隔符是 \,Unix 是 /。如果缓存 key 里直接写 ~ 路径,不同 OS 的 key 不匹配:

# 错误:Windows 和 Unix 的缓存路径不同,无法复用
- name: Cache pip packages
  uses: actions/cache@v4
  with:
    path: ~/Library/Caches/pip  # macOS
    # Windows: ~$LOCALAPPDATA$\pip\Cache
    # Linux: ~/.cache/pip
    key: pip-${{ matrix.os }}-${{ hashFiles('requirements.txt') }}

正确做法:用 runner.os 区分不同 OS 的缓存路径:

- name: Cache pip packages
  uses: actions/cache@v4
  with:
    path: |
      ~/.cache/pip
      ~/Library/Caches/pip
      ~\AppData\Local\pip\Cache
    key: |
      pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
    restore-keys: |
      pip-${{ runner.os }}-

现在你可以做什么

第一步:在你的项目里新建 .github/workflows/scheduled-test.yml,把上面的冒烟测试矩阵复制进去;

第二步:执行 pip-compile requirements.in --generate-hashes 生成锁文件,提交到仓库;

第三步:把 cron 表达式从 '0 8 * * 1' 改成你需要的北京时间,用 date -u 验证 UTC 时间是否对应正确;

第四步:如果 release 时需要跑完整矩阵,在 on.push.tags 里加上 paths: ['requirements.txt', 'requirements.in'],只在依赖文件变更时触发。

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享