跳转至

接口自动化持续集成与 Allure 报告

本文给两套能直接跑的配置:GitHub Actions 与 Jenkins,并列出真正会让流水线“红得莫名其妙”的细节。

1. 本地先确认三件事

# 1. 用例本身可重复执行
pytest -m smoke -q
pytest -m smoke -q          # 连跑两次结果应一致

# 2. 报告能生成
pytest -m smoke --alluredir=allure-results
allure generate allure-results -o allure-report --clean
allure open allure-report

# 3. 退出码正确
pytest -m smoke; echo "exit=$?"

第 3 步很关键:很多流水线“绿”是因为 pytest | tee 吞掉了退出码。

2. GitHub Actions:带数据库依赖

name: api-tests

on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: "0 18 * * *"   # UTC 18:00 = 北京时间 02:00

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

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: test
          POSTGRES_DB: qa
        ports: ["5432:5432"]
        options: >-
          --health-cmd "pg_isready -U postgres"
          --health-interval 5s
          --health-timeout 5s
          --health-retries 20

    env:
      BASE_URL: ${{ secrets.BASE_URL }}
      API_USER: ${{ secrets.API_USER }}
      API_PASSWORD: ${{ secrets.API_PASSWORD }}
      DB_DSN: postgresql://postgres:test@localhost:5432/qa
      TZ: Asia/Shanghai

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: pip
          cache-dependency-path: requirements.txt

      - name: Install
        run: pip install -r requirements.txt

      - name: Wait for DB
        run: |
          for i in $(seq 1 30); do
            pg_isready -h localhost -p 5432 -U postgres && exit 0
            sleep 2
          done
          echo "db not ready" && exit 1

      - name: Run tests
        run: |
          set -o pipefail
          pytest -m "smoke or regression" -n 4 \
            --alluredir=allure-results \
            --junitxml=reports/junit.xml

      - name: Upload results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: allure-results
          path: |
            allure-results
            reports
          retention-days: 14

这里藏着的四个坑

坑 现象 解法
服务健康检查缺失 could not connect to server: Connection refused options 里加 health-cmd,并显式 wait 循环
set -o pipefail 漏写 用例失败但 job 绿 每个 run 里加 set -o pipefail
if: always() 漏写 失败时没有报告可看 上传步骤必须 if: always()
定时任务按 UTC 凌晨跑到了前一天 cron 用 UTC,显式换算

PR 来自 fork 时拿不到 secrets,这是 GitHub 的安全设计。解法:fork 的 PR 只跑不需要密钥的 lint + 单测;接口回归在内部 PR 或合并后触发。

3. Allure 报告发布

只上传 artifact,查看要下载解压,体验差。两种发布方式:

3.1 GitHub Pages(保留历史)

      - name: Generate report
        if: always()
        run: |
          curl -sLo allure.tgz https://github.com/allure-framework/allure2/releases/download/2.30.0/allure-2.30.0.tgz
          tar -xzf allure.tgz
          ./allure-2.30.0/bin/allure generate allure-results -o allure-report --clean

      - name: Deploy
        if: always()
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./allure-report
          destination_dir: reports/${{ github.run_number }}

每次新目录会导致历史趋势断开。要保留趋势,需在生成前把上一版的 history 拉回来:

curl -sL https://<user>.github.io/<repo>/reports/latest/history.json \
  -o allure-results/history.json || true

3.2 Jenkins Allure 插件(自带历史)

post {
  always {
    allure includeProperties: false,
    jdk: '',
    results: [[path: 'allure-results']],
    reportBuildPolicy: 'ALWAYS'
  }
}

插件会把 history 存在构建目录里,趋势图自动累积。不要在测试前 cleanWs(),否则历史被清空。

4. 让报告可定位:step 与附件

import allure

@allure.feature("订单")
@allure.story("创建订单")
@allure.severity(allure.severity_level.CRITICAL)
def test_create_order(client, order_api):
    with allure.step("准备商品库存"):
        sku = ensure_stock(1001, 10)

    with allure.step("调用创建订单接口"):
        resp = order_api.create(sku_id=1001, count=2)
        allure.attach(resp.text, "response", allure.attachment_type.JSON)

    with allure.step("校验业务码与状态"):
        body = resp.json()
        assert body["code"] == 0, body
        assert body["data"]["status"] == "PENDING"

失败时,报告里能直接看到失败发生在哪个 step、当时的响应体。没有 step 的报告,只有一个红色的用例名。

5. 并行与分片

# 单机并行
pytest -n 4 --dist loadscope --alluredir=allure-results

# 多机分片(每个 job 一个 group)
# 注意:pytest 本身没有 --shard,需安装 pytest-split
pip install pytest-split
pytest --splits 3 --group 1 --alluredir=allure-results-1

--dist loadscope 保证同一模块的用例落在同一个 worker,减少共享 fixture 被重复初始化的开销。

不要写 pytest --shard=1/3,这是 Playwright runner 的参数,pytest 不识别。用 pytest-split 的 --splits/--group,或 pytest-shard 的 --shard-id/--num-shards。

并行后必须处理:

  • 数据唯一性(worker_id 或 shard 后缀)
  • 每个 shard 的结果目录不能互相覆盖(allure-results-1、-2),最后合并
  • 重试用例会产生重复结果,Allure 里显示两条;用 --reruns 时要接受这一点,或只在失败时重跑并标记

6. 质量门禁

# conftest.py:失败率超过阈值直接让流水线红
def pytest_sessionfinish(session, exitstatus):
    total = session.testscollected
    failed = session.testsfailed
    if total and failed / total > 0.05:
        session.exitstatus = 1
// Jenkins:执行时长超预算告警
script {
  def duration = currentBuild.duration / 1000 / 60
  if (duration > 20) { echo "WARN: 测试耗时 ${duration} 分钟,超过预算" }
}

7. 通知:结论 + 链接

curl -s -X POST "$WEBHOOK" -H 'Content-Type: application/json' -d @- <<'JSON'
{
  "msgtype": "markdown",
  "markdown": {
    "content": "**接口回归完成**\n> 通过 118 / 失败 2 / 跳过 0\n> [查看 Allure 报告](https://ci.example.com/report)"
  }
}
JSON

不要在通知里贴堆栈,链接过去看。

8. 排查表

现象 原因 处理
报告空白 allure-results 路径不对或被清理 用绝对路径,测试后不要 clean
趋势图每次归零 history 未保留 Jenkins 插件保留,或手动恢复 history.json
用例在 CI 全挂,本地全过 环境变量/时区/数据差异 打印 env 与 TZ,比对 BASE_URL
随机 401 多 worker 共用一个账号/被踢 按 worker 分配账号
偶发超时 服务启动慢 加健康检查与启动等待,别用固定 sleep
流水线 40 分钟 串行执行 + 无缓存 分片、pip cache、只跑变更相关用例

9. 验收

□ 连续 10 次构建,Allure 有完整趋势
□ 故意失败一条断言,构建变红且报告定位到 step
□ 删掉一条基础数据,用例报错而非假绿
□ 并行 4 片跑两次,结果一致
□ 失败构建的 artifact 保留 ≥ 14 天

相关阅读