数据库双检|质量门禁|飞书通知|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 个接口 |
三、架构演进:从"裸奔脚本"到"工程化框架"
这个框架经历了三个版本的迭代,每一版都解决了一个真实痛点:
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 报告里会清晰展示三步拆解:
这个功能帮我发现了好几个"接口假成功"的 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 服务后:
- Wireshark 选择
Adapter for loopback traffic capture - 过滤器输入
tcp.port == 5000 - 跑测试用例
- 右键 HTTP 包 → 追踪流 → HTTP 流
| 情况 | 结论 |
|---|---|
| 请求参数与预期不一致 | Bug 在测试脚本 |
| 请求正确,后端返回异常 | Bug 在服务端 |
HTTP 流完整对话:
数据包列表:
八、踩过的坑(希望你别踩)
下面这几个坑,我每个都至少踩了 2 小时。
| 问题 | 原因 | 解决方案 |
|---|---|---|
| POST 新增后查不到数据 | JSONPlaceholder 仅模拟成功,不持久化 | 自研 Flask + SQLite Mock 服务 |
| 公网接口偶发失败 | 网络抖动 | tenacity 自动重试 2 次 |
并发执行 FileExistsError | 多个 worker 同时创建目录 | dirs_exist_ok=True + 异常捕获 |
Docker 容器缺少 jsonschema | requirements.txt 漏了包 | 补上 jsonschema>=4.0.0 |
| Wireshark 抓不到 5000 端口 | 网卡选错了 | 必须选回环网卡 |
| 质量门禁统计失真 | 旧 allure-results 未清理 | CI 每次构建前强制清理 |
飞书 post 消息不支持 lark_md 标签 | 消息类型和标签不匹配 | 改用 interactive 卡片消息 |
九、自学建议:如果你也想搭一个
-
先跑起来,再谈优化 别纠结完美架构,能跑通一个用例就算成功。我第一版只有 5 个用例,结构也很丑。
-
用好 Allure 报告 看到每一步的执行过程,比光看终端输出更容易理解问题在哪。
-
数据库双检值得做 你会发现很多"接口通了但数据没写进去"的坑,这在真实项目里很常见。
-
随手写文档 自己写的代码,一周后可能就忘了。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 ⭐
参考资源
- Pytest 官方文档
- Requests 官方文档
- Allure 官方文档
- Playwright 官方文档(同作者的 UI 自动化项目也在用)
你在搭建测试框架时遇到过哪些坑?欢迎在评论区交流 👇