64 用例 10 秒跑完:我用 Pytest + Allure 搭了一个工程化接口测试框架

80 阅读8分钟

数据库双检|质量门禁|飞书通知|Docker 一键运行

从 0 到 64 用例、100% 有效通过率、10 秒全量回归——记录我搭建工程化接口自动化框架的完整过程。包含数据库双检、质量门禁、飞书通知、Docker 一键运行等 15 项核心能力。


一、学完基础后,我发现还差一步

学完 Pytest 和 Requests 基础后,我想找个项目把知识串起来。但看了一圈教程,发现它们大多是入门级的示例,离能跑在 CI 上的工程化框架还差几步:

  • 大多数教程只教你怎么 requests.get() 再 assert,没有工程化结构
  • 用公网 Mock 接口(如 JSONPlaceholder)做练习,POST 新增后根本查不到数据——它只模拟成功响应,不持久化
  • 缺少 Allure 报告、CI、通知这些工程化必备的环节

我决定自己搭一个能真正跑在 CI 上、能发现真实 Bug、能一键部署的框架。

项目已开源: github.com/StillStream…


二、成果速览

两周时间,从 0 到能跑通全量回归:

指标数据
业务模块5 个(文章 / 商品 / 订单 / 评论 / 用户)
测试用例64 个(59 通过 + 5 跳过)
全量回归耗时~10 秒
有效通过率100%
核心用例评分20/20(满分)
Schema 校验覆盖32 个接口

allure_overview.png


三、架构演进:从"裸奔脚本"到"工程化框架"

这个框架经历了三个版本的迭代,每一版都解决了一个真实痛点:

V1:工程化框架(外部 API)

痛点: 公网 Mock 接口网络抖动,CI 随机失败。

解法:

  • 统一 HTTP 封装,集成 tenacity 失败自动重试
  • YAML 数据驱动,接口定义与用例解耦
  • Pytest Fixture 实现上下游参数自动传递

问题: JSONPlaceholder 的 POST 只返回假数据,数据库里根本没有这条记录。

V2:本地 Mock + 质量门禁

痛点: 外部 API 不稳定,数据不持久化,无法做数据库断言。

解法:

  • 自研 Flask + SQLite Mock 服务,数据真实落库
  • 引入数据库双检:接口返回 200 后,再去 DB 里查一遍
  • 质量门禁脚本:通过率低于 90% 自动阻断 CI
  • 多环境一键切换:API_ENV=test pytest 即可切换环境

V3:进阶能力

痛点: 随着用例增加到 60+,串行执行太慢,报告难以追溯,团队感知滞后。

解法:

  • pytest-xdist 并发执行,效率提升 10 倍以上
  • JSON Schema 校验,防止接口偷偷改字段类型
  • 测试数据自动清理,每次跑完重置数据库
  • 报告按时间戳自动归档到 reports/
  • 用例评分系统(0-20 分),综合结果、耗时、重跑次数
  • 飞书自动通知,测试完成后推送统计报告
  • Wireshark 抓包调试,快速定位是脚本问题还是后端问题
  • 敏感信息环境变量化,飞书 Webhook 和数据库密码从环境变量读取,不硬编码
graph LR
    A[Pytest 用例层<br/>64 cases] --> B[HTTP 封装层<br/>tenacity 重试]
    B --> C[Flask Mock 服务<br/>SQLite 持久化]
    B --> D[数据库双检<br/>接口+DB一致性]
    C --> E[(SQLite / MySQL)]
    D --> E
    B --> F[Allure 报告<br/>步骤拆解+附件]
    F --> G[飞书通知]
    F --> H[质量门禁<br/>90% 阈值阻断]
    B --> I[pytest-xdist<br/>并发加速]
    B --> J[JSON Schema<br/>结构校验]

四、三个我觉得最值得分享的实现

1. 数据库双检:接口通了,数据真的写进去了吗?

这是我框架里最有特色的功能。很多接口返回 200,但数据库里根本没这条记录。

我在 common/db_checker.py 里封装了一个双检函数:

def check_and_assert(table_name, record_id, expected_fields):
    conn = get_db()
    cursor = conn.cursor()
    cursor.execute(
        f"SELECT * FROM {table_name} WHERE id = ?", (record_id,)
    )
    row = cursor.fetchone()

    assert row is not None, "数据库里找不到这条记录"

    for key, value in expected_fields.items():
        assert row[key] == value, f"字段【{key}】不一致"

    conn.close()

然后在用例里这样用:

@allure.feature("文章模块")
@allure.story("新增文章")
def test_create_post():
    # 1. 调接口
    resp = send_request("POST", f"{BASE_URL}/posts", 
                        json_data={"title": "测试", "body": "内容", "userId": 1})
    assert resp.status_code == 201
    data = resp.json()

    # 2. Schema 校验(结构对不对)
    validate_with_step(data, post_create_schema, "创建文章")

    # 3. 数据库双检(数据真落库了吗)
    check_and_assert("posts", data["id"], {"title": "测试"})

Allure 报告里会清晰展示三步拆解:

allure_step_detail.png

这个功能帮我发现了好几个"接口假成功"的 Bug。


2. 质量门禁:CI 不达标,自动阻断发布

在 common/quality_gate.py 里,我解析 Allure 结果计算有效通过率(跳过的不计入分母):

def check_quality_gate(threshold=0.90):
    results = parse_allure_results("allure-results")
    valid_total = results["passed"] + results["failed"]
    if valid_total == 0:
        return False

    rate = results["passed"] / valid_total
    if rate < threshold:
        print(f"❌ 质量门禁未通过({rate:.2%} < {threshold:.0%})")
        sys.exit(1)

    print(f"✅ 质量门禁通过(有效通过率 {rate:.2%} > 阈值 {threshold:.0%})")
    return True

GitHub Actions 里配置为最后一步:

- name: Quality Gate
  run: python common/quality_gate.py

如果通过率低于 90%,整个 CI 流水线会变红阻断,防止低质量代码合入主干。


3. 并发加速:64 个用例从 2 分钟压到 10 秒

集成 pytest-xdist 后,用例并行执行:

# 串行:~120 秒
pytest cases/

# 并行(自动根据 CPU 核数):~10 秒
pytest cases/ -n auto

注意: 并发时多个 worker 会同时操作数据库和创建目录,需要处理好资源竞争:

# 目录创建加异常捕获
shutil.copytree(src, dst, dirs_exist_ok=True)

# 数据库文件操作加重试机制

五、效果对比:前后数据一览

指标V1(外部 API)V3(当前)提升
全量回归耗时~120 秒~10 秒12x
网络失败率~15%(偶发超时)0%完全消除
环境搭建需外网 + 手动配环境docker-compose up一键启动
报告可追溯性终端文本输出请求/响应/DB/日志全留痕完整链路
失败定位靠 print 猜Wireshark 抓包 + Allure 附件精准定位
团队感知跑完没人知道飞书群自动推送实时同步

六、Docker 一键运行:解决"在我电脑上能跑"

docker-compose.yml 配置了三个服务:

version: '3.8'
services:
  mock:
    build: .
    ports:
      - "5000:5000"
    environment:
      - API_ENV=docker

  test:
    build: .
    depends_on:
      - mock
    command: pytest cases/ -v
    volumes:
      - ./reports:/app/reports

任何电脑只要装了 Docker:

docker-compose up --abort-on-container-exit

自动拉起 Mock 服务 → 等待就绪 → 跑完全部 64 个用例 → 生成 Allure 报告 → 自动退出。


七、Wireshark 抓包:定位问题不再靠猜

当用例报错时,启动 Mock 服务后:

  1. Wireshark 选择 Adapter for loopback traffic capture
  2. 过滤器输入 tcp.port == 5000
  3. 跑测试用例
  4. 右键 HTTP 包 → 追踪流 → HTTP 流
情况结论
请求参数与预期不一致Bug 在测试脚本
请求正确,后端返回异常Bug 在服务端

HTTP 流完整对话:

debug_http_stream.png

数据包列表:

debug_packet_list.png


八、踩过的坑(希望你别踩)

下面这几个坑,我每个都至少踩了 2 小时。

问题原因解决方案
POST 新增后查不到数据JSONPlaceholder 仅模拟成功,不持久化自研 Flask + SQLite Mock 服务
公网接口偶发失败网络抖动tenacity 自动重试 2 次
并发执行 FileExistsError多个 worker 同时创建目录dirs_exist_ok=True + 异常捕获
Docker 容器缺少 jsonschemarequirements.txt 漏了包补上 jsonschema>=4.0.0
Wireshark 抓不到 5000 端口网卡选错了必须选回环网卡
质量门禁统计失真旧 allure-results 未清理CI 每次构建前强制清理
飞书 post 消息不支持 lark_md 标签消息类型和标签不匹配改用 interactive 卡片消息

九、自学建议:如果你也想搭一个

  1. 先跑起来,再谈优化 别纠结完美架构,能跑通一个用例就算成功。我第一版只有 5 个用例,结构也很丑。

  2. 用好 Allure 报告 看到每一步的执行过程,比光看终端输出更容易理解问题在哪。

  3. 数据库双检值得做 你会发现很多"接口通了但数据没写进去"的坑,这在真实项目里很常见。

  4. 随手写文档 自己写的代码,一周后可能就忘了。README 不只是给别人的,更是写给三个月后的自己看的。 你当时为什么这么设计、踩过什么坑,不写下来就全忘了。


十、知识地图:按这个顺序复刻

如果你想复刻这个框架,建议按这个路径学习:

Week 1
├── Day 1-2: Pytest 基础(fixture、参数化、mark)
├── Day 3-4: Requests 封装 + 简单断言
├── Day 5-6: Allure 报告集成(step、attach、feature/story)
└── Day 7: 跑通第一个端到端用例

Week 2
├── Day 8-9: Flask Mock 服务 + SQLite 持久化
├── Day 10-11: 数据库双检 + Schema 校验
├── Day 12-13: pytest-xdist 并发 + 质量门禁脚本
└── Day 14: Docker + GitHub Actions CI 集成

写在最后

这个项目是我自学过程中的一个练习,代码和文档可能还有不完善的地方,欢迎交流指正。

如果对你有点帮助,欢迎点个 Star ⭐


参考资源


你在搭建测试框架时遇到过哪些坑?欢迎在评论区交流 👇