接口自动化持续集成与 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拉回来:
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 天