小Cの已经记不起来的博客

用 GitHub Actions 定时运行后端集成测试并保存报告

最近给后端仓库补了一些集成测试,本地跑着倒是没问题,但问题是你不可能每天都记得去跑一遍。尤其是那种依赖数据库、缓存、第三方接口 mock 的测试,平时单元测试根本覆盖不到。后来我索性把这件事丢给 GitHub Actions,让它每天定时跑一次,跑完把报告存下来。踩了几个小坑,这里记录一下,免得下次又翻车。

先想清楚要跑什么

我一开始图省事,直接把整个测试套件都塞进定时任务里,结果每次跑二十多分钟,报告里还混着一堆单元测试,看着很乱。后来改成只跑带 integration 标记的用例,单元测试还是留在提交时跑。

命令大概是这样:

pytest -m integration -ra --junitxml=reports/integration.xml --html=reports/integration.html --self-contained-html

这里顺手装了 pytest-html,因为 JUnit XML 虽然很多工具都能解析,但人看着确实不太直观。HTML 报告丢到 artifact 里,点开就能看哪条用例挂了、堆栈是什么。--self-contained-html 也别省,不然 CSS 和图片是外链,下载下来容易缺东西。

准备数据库这些依赖

集成测试一般离不开数据库。GitHub Actions 里可以用 services 起一个 PostgreSQL,没必要去连外部环境。我现在的写法大概是这样:

services:
  postgres:
    image: postgres:16
    env:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_DB: app_test
    ports:
      - 5432:5432
    options: >-
      --health-cmd "pg_isready -U test -d app_test"
      --health-interval 5s
      --health-timeout 5s
      --health-retries 10

这个 health check 挺重要的,不然数据库还没起来,测试已经开始连了,然后就是一堆连接失败。虽然可以在测试前自己写个等待循环,但能让 service 自己报告健康状态就省事很多。

如果项目还依赖 Redis、MinIO 之类的,也可以照这个思路继续加 service。端口映射到 runner 的 localhost,测试配置里直接用就行。

写 workflow 文件

workflow 放在 .github/workflows/nightly-integration.yml,名字随便取,我这边叫 Nightly Integration Tests。

name: Nightly Integration Tests

on:
  schedule:
    - cron: '0 18 * * *'
  workflow_dispatch:

jobs:
  integration:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: app_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd "pg_isready -U test -d app_test"
          --health-interval 5s
          --health-timeout 5s
          --health-retries 10

    env:
      DATABASE_URL: postgresql://test:test@localhost:5432/app_test
      APP_ENV: test

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: pip

      - name: Install dependencies
        run: |
          python -m pip install -U pip
          pip install -r requirements.txt
          pip install pytest pytest-html

      - name: Prepare database
        run: |
          # 这里换成你项目自己的迁移或建表命令
          # 比如 python manage.py migrate --noinput
          echo "prepare database"

      - name: Run integration tests
        run: |
          mkdir -p reports
          pytest -m integration -ra \
            --junitxml=reports/integration.xml \
            --html=reports/integration.html \
            --self-contained-html

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: integration-report
          path: reports/
          retention-days: 14

这里有几个地方我觉得值得说一下。

第一,workflow_dispatch 最好加上。定时任务出问题的时候,你可以手动点一次跑,不用等第二天。调试 workflow 的时候特别有用。

第二,if: always() 一定要加在 upload-artifact 上。我一开始没加,结果测试失败的时候,后面上传报告那一步直接跳过了,artifacts 里空空如也。后来才反应过来,测试失败时默认后续 step 不执行,必须用 always() 才能把失败报告也存下来。这个坑挺蠢的,但确实容易忘。

第三,mkdir -p reports 别省。虽然 pytest 生成报告时会自己建目录,但如果你中间加了别的命令,或者路径写错,先建目录能少排查一点。

定时任务和时区

GitHub Actions 的 cron 用的是 UTC,不是北京时间。我上面写的 0 18 * * *,实际是北京时间第二天凌晨 2 点。如果你想每天早上 9 点跑,那大概得写 0 1 * * *。这个换算第一次看容易懵,建议直接在 workflow 里写注释,不然后面自己都忘了。

另外,schedule 只会在默认分支上生效。你在功能分支里改 cron,不合并到默认分支是不会按新时间跑的。还有就是 GitHub 的定时任务不保证完全准时,忙的时候延迟几分钟甚至更久都正常,所以别拿它当特别精确的闹钟用。

报告保存和失败排查

现在每次跑完,Actions 页面里会有一个 integration-report 的 artifact,里面包含 integration.xml 和 integration.html。我一般先看 HTML,挂了哪条用例、错误堆栈、截图之类的一眼就能看到。XML 可以留着给以后接测试报告平台用,虽然目前我还没接。

如果测试失败,除了报告,Actions 日志里也能看到 pytest 的输出。我习惯在本地先跑一遍同样的命令,确认是不是环境问题。很多时候不是代码挂了,而是数据库迁移没跑、环境变量没配、或者测试依赖的外部服务地址写死了。定时任务的好处是它每天都用一套干净环境跑,反而更容易暴露这些问题。

还有一点,私有仓库跑 Actions 会消耗额度,定时任务别设得太频繁。一天一次或者工作日每天一次就够了。报告保留天数也没必要设太长,我设的是 14 天,出问题的时候够翻就行。

这套东西跑起来之后,至少不用靠人记得去点测试了。哪天数据库升级、依赖版本变化、或者某个接口悄悄改了行为,第二天的报告里基本都能看出来。省事是真省事,前提是把 if: always() 和时区这两个坑先填上。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放