返回自动化工程模块
Automation / Tutorial 15

持续测试与 CI/CD 工程教程

让每次代码变化都尽早获得可信反馈,让失败能归因、报告能追溯、风险能阻断。

10 个章节商城发布流水线GitHub Actions
01

把测试放进交付反馈环

越早反馈越便宜
一次订单服务变更的持续反馈
提交本地快速检查
PR冒烟与契约
合并核心回归
发布门禁与灰度
线上指标回流

持续测试不是持续跑全量

优惠金额函数改动后,先在分钟级验证单元与接口规则;进入候选版再验证订单、库存、支付、退款的完整链路。目标是用最小成本尽快暴露当前变更最可能引入的风险。

接口 HTTP 200 只表示请求被正常处理,流水线中的断言仍必须检查响应体业务码、订单状态、金额与副作用。
02

按风险、速度和触发时机分层

冒烟 回归 全量

一套测试,不同反馈层

触发测试范围目标时长商城示例
提交前单元、静态检查秒~3 分钟金额、优惠、状态机
PR冒烟 + 契约 + 核心接口5~15 分钟下单、锁库存、支付
合并后主路径回归15~40 分钟优惠、取消、退款
夜间/候选版全量 + 多环境专项按项目规模兼容、性能、长尾组合
pytest 与 Playwright 标签
# pytest.ini
[pytest]
markers =
    smoke: 核心交易冒烟
    regression: 主要业务回归

# 运行
python -m pytest -m smoke
python -m pytest -m "regression and not slow"
npx playwright test --grep @smoke
冒烟不是随机挑几条快用例。它必须证明版本可继续测试:商城可登录、商品可查、订单可创建、支付依赖可达,且关键响应体结果正确。
03

用 GitHub Actions 建立可执行流水线

配置可复现
.github/workflows/quality.yml
name: quality
on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: pip
      - run: pip install -r requirements-test.txt
      - name: Run smoke and regression
        env:
          BASE_URL: ${{ secrets.TEST_BASE_URL }}
          TEST_TOKEN: ${{ secrets.TEST_TOKEN }}
        run: python -m pytest -m "smoke or regression" --junitxml=reports/junit.xml
      - name: Archive test evidence
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-report-${{ github.run_id }}
          path: reports/
          retention-days: 14

配置原则

  • 锁定运行时版本与依赖,避免本地和 CI 漂移。
  • 令牌只从仓库 Secret 注入,不写入代码和日志。
  • 设置超时,避免挂起任务长期占用资源。
  • 报告上传使用 if: always(),失败时也保留证据。
04

让 PR 检查跟随变更风险

合并前阻断
PR 从变化到结论
识别路径订单或优惠
选择检查静态到E2E
并行执行缩短等待
汇总证据状态与报告
允许或阻断规则一致
按路径触发交易测试
on:
  pull_request:
    paths:
      - "services/order/**"
      - "services/inventory/**"
      - "services/payment/**"
      - "tests/**"
      - ".github/workflows/quality.yml"

Branch protection 应要求

  • 必需检查名称固定且不能被跳过。
  • 新提交会使旧审批和旧结果失效。
  • 高风险交易改动至少由代码负责人复核。
  • 管理员绕过也应记录原因与审批人。
05

让环境和测试数据可重复

隔离并可清理
并行测试唯一标识
import os
from uuid import uuid4

RUN_ID = os.getenv("GITHUB_RUN_ID", "local")

def unique_key(prefix: str) -> str:
    return f"{prefix}-{RUN_ID}-{uuid4().hex[:8]}"

# 订单备注、优惠码和测试账号都带运行标识
coupon_code = unique_key("CI-COUPON")

准备

  • 通过受控测试接口创建商品、库存、优惠和账号。
  • 运行记录版本、配置摘要与依赖健康状态。
  • 并行任务使用独立租户或唯一标识。
  • 资金类动作只进入隔离沙箱。

清理

  • 按本次运行 ID 精确清理。
  • 清理失败写入报告,不做全库删除。
  • 需要审计的订单保留并标记过期。
  • 环境漂移时立即中止并标记为环境问题。
06

先归因,再决定重跑或阻断

失败不是一个红点

失败归因决策表

类别证据行动
产品缺陷相同版本和数据可重复,结果违反业务规则阻断并关联缺陷
测试缺陷定位、等待、断言或清理错误修复脚本后重跑受影响范围
环境问题服务不可用、配置漂移、容量不足恢复环境并保留运行证据
数据问题库存被占用、优惠过期、账号污染修复数据工厂与隔离
不稳定测试相同提交多次运行结果不一致隔离、统计、限期治理
为每次调用保留关联信息
response = order_client.create_order(payload)
logger.info(
    "create_order finished",
    extra={
        "run_id": run_id,
        "commit": commit_sha,
        "order_id": response.json().get("data", {}).get("orderId"),
        "business_code": response.json().get("businessCode"),
    },
)
assert response.status_code == 200
assert response.json()["businessCode"] == "SUCCESS"
重跑通过不等于首次失败消失。先保存原始日志、请求关联 ID、截图或 trace,再重跑确认是否不稳定。
07

把 Flaky Test 当作工程缺陷治理

统计 修复 退出
不稳定用例治理闭环
识别同提交结果漂移
隔离不掩盖主结果
收集时间与证据
修复等待数据环境
退出隔离连续稳定

治理规则

  1. 用相同提交、环境和数据重复确认,不凭一次重跑下结论。
  2. 为用例登记负责人、首次出现时间、失败率和业务风险。
  3. 高风险冒烟用例即使 Flaky 也不能静默放行,应优先修复或替换。
  4. 临时隔离必须设置期限;隔离套件仍定时运行并通知负责人。
  5. 只有找到原因且达到约定的连续稳定次数后,才退出隔离。
有限重试并保留首次失败
# pytest.ini
[pytest]
addopts = --reruns 1 --reruns-delay 1

# Playwright
retries: process.env.CI ? 1 : 0,
trace: 'retain-on-failure'
08

归档报告,并把通知发给能行动的人

证据可追溯

报告至少包含

  • 仓库、提交 SHA、分支、工作流与运行链接。
  • 环境、版本、数据批次和触发人。
  • 通过、失败、跳过、重试与 Flaky 数量。
  • 订单号、请求关联 ID、失败步骤和关键响应体。
  • JUnit、HTML、截图、Trace 和服务日志。
失败后通知示意
- name: Notify failure
  if: failure()
  env:
    WEBHOOK_URL: ${{ secrets.QA_WEBHOOK_URL }}
  run: |
    python scripts/notify.py       --status failed       --run-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"       --report "test-report-${{ github.run_id }}"
通知应包含“谁需要做什么”和运行链接,不能只发送“测试失败”。Webhook、Token 和用户数据不得出现在报告正文。
09

把流水线结果转换为发布门禁

证据驱动放行

商城交付的关键门禁

阶段必需证据阻断条件
PR 合并lint、类型、单元、冒烟任何必需检查失败
测试环境部署迁移检查、健康检查版本或依赖不一致
候选版P0/P1 回归、已知风险评审交易核心失败或证据缺失
生产发布审批、灰度、监控、回滚不可观测或不可回退
环境保护与人工审批
jobs:
  deploy-production:
    needs: [test]
    if: github.ref == 'refs/heads/main'
    environment: production
    runs-on: ubuntu-latest
    steps:
      - run: ./scripts/deploy.sh
      - run: ./scripts/verify-smoke.sh
生产 environment 应在仓库设置中配置审批人和保护规则。配置文件表达依赖关系,审批权限不能靠脚本里的注释代替。
10

完成一条可诊断的持续测试流水线

练习与验收

练习:为商城交易服务建立 CI/CD

  1. 把单元、冒烟、回归和全量测试按触发时机分层。
  2. 创建 GitHub Actions 工作流,锁定 Python 版本并缓存依赖。
  3. 在 PR 中并行执行静态检查、单元测试和交易冒烟。
  4. 使用 Secret 注入测试地址和令牌。
  5. 故意制造产品、脚本、环境、数据四类失败并完成归因。
  6. 为一条偶发等待用例建立 Flaky 登记、隔离和退出标准。
  7. 无论成功失败都归档 JUnit、HTML 与 trace,设置保留期限。
  8. 发送包含负责人行动和运行链接的失败通知。
  9. 配置候选版与生产门禁,演练一次阻断和一次回滚。

反馈够快

  • 分层有依据
  • PR 时长受控
  • 任务可并行
  • 缓存不污染

失败可信

  • 原始证据保留
  • 归因规则一致
  • 重试次数有限
  • Flaky 有负责人

发布可控

  • 门禁不可静默跳过
  • 报告可追溯
  • 通知可行动
  • 灰度回滚就绪

流水线已经能持续验证单个应用。下一步进入服务依赖、故障传播和分布式事务。

继续学习微服务测试