P0-P3 落地实录(三):从测试隔离到并发竞态,1109 个测试全绿以后我又发现了什么?

发布时间:2026/9/26 3:14:08
P0-P3 落地实录(三):从测试隔离到并发竞态,1109 个测试全绿以后我又发现了什么? AutoPilot Test V1.1 工程化治理系列前两篇分别写了数据库迁移问题和执行状态问题。到了这一篇我开始问一个更绕的问题如果 AutoPilot 已经有 1109 个通过的测试为什么我还是不敢直接说“它已经没问题了”答案其实很简单测试体系本身也可能存在 Bug。测试可以通过但测试之间可能互相污染Mock 可以全绿但真实浏览器未必真的能跑并发场景如果没有专门验证生产代码一样可能存在竞态。这一篇就是我在 V1.1 收口阶段做的最后一轮测试工程化排查。一、1109 passed为什么我没有立刻庆祝AutoPilot 的后端测试按几个层次组织unit services routers integration项目里的测试大量使用 SQLite并通过 Mock 隔离 LLM、Playwright、Appium、文件系统等外部依赖。在这一轮 V1.1 收口的完整回归记录里结果是1109 passed 2 skipped同时还有compileallOK Real Chromium Mock LLM 黄金路径通过数字确实很好看。但我没有把它理解成“1109 个测试通过所以系统没有 Bug。”我更愿意把它理解成1109 个已验证的断言通过。这两句话差别很大。二、第一个问题为什么测试单独跑 PASS组合跑却可能 FAIL这是测试工程里很危险的一类问题。理想状态应该是Test A ↓ 不会改变 ↓ Test B 看到的环境但在 AutoPilot 的测试体系里我碰到了一个很典型的场景。部分模型测试会执行PRAGMA foreign_keysON而测试数据库使用共享连接模型。测试结束以后虽然做了rollback但PRAGMA属于连接级状态并不会因为事务回滚自动恢复。于是可能出现Test A ↓ 打开 foreign_keys ↓ rollback ↓ 连接级 PRAGMA 还在 ↓ Test B ↓ 外键行为发生变化 ↓ FAIL这里最容易出现的误区是“都 rollback 了怎么还会污染”答案就是事务状态和连接级状态不是一回事。三、最终怎么处理让公共 Fixture 对环境负责我没有让每个测试自己记得写PRAGMA foreign_keysOFF而是把清理动作放进公共db_sessionfixture 的 teardown。最终逻辑是测试开始 ↓ 创建连接 / Session ↓ 执行测试 ↓ rollback ↓ 恢复连接级状态 ↓ 关闭连接测试 Fixture 中最终有类似try:connection.execute(text(PRAGMA foreign_keys OFF))exceptException:pass这个改动看起来很小但它带来的工程原则很重要测试可以修改共享环境但必须负责把环境还原。四、第二个测试隔离问题数据库迁移居然还能影响日志环境这次排查里我特别喜欢的一点是它让我意识到测试隔离绝对不只是数据库回滚。项目的 Alembic 环境会使用fileConfig(...)而 migration 在 pytest 进程里运行时可能修改当前进程里的 logging 状态。于是可能出现Test A ↓ 执行 Alembic ↓ logger 配置被修改 ↓ Test B ↓ pytest 的日志捕获行为发生变化本来只是一个数据库迁移测试最后却影响了后面所有测试看到的日志环境。五、这个问题最后怎么处理我用了两层防线。第一层在 Alembic 环境里显式设置fileConfig(config.config_file_name,disable_existing_loggersFalse,)它的意义是避免 migration 配置把既有 logger 简单粗暴地全部禁用。第二层是在相关测试里做 logging 状态快照和恢复包括root logger logger level handlers disabled propagate这样即使测试过程改变了 logging 状态测试结束以后也会恢复现场。这个问题最后给我的结论非常简单只要测试改了环境就应该由测试负责恢复环境。六、第三个问题Mock 测试全部正常真实 Chromium 却炸了前一轮我已经遇到过一次这个问题所以这次把它正式纳入“真实黄金路径”验证。AutoPilot 的SafePlaywright会限制 AI 生成代码访问底层 Playwright 对象。设计目标是AI 代码 ↓ 白名单 API ↓ 不能直接拿到底层 page ↓ 不能通过私有属性轻易绕过限制问题在于Playwright 自己在真实 API 调用时也会进行调用栈分析。实际内部会读取frame.f_locals[self].__class__.__name__而原来的SafePlaywright为了安全把__class__也拦截了。于是Mock ↓ 全部通过 Real Chromium ↓ Playwright 真实内部逻辑 ↓ 访问 __class__ ↓ 被 SafePlaywright 拦截 ↓ 真实执行失败这就是为什么我越来越不愿意把 Mock 全绿直接当成真实环境全绿。七、最终怎么修安全边界不能以破坏底层框架为代价这次没有直接把__class__全部放开。而是提供了一个极小能力的 class proxy_SAFE_CLASS_PROXYtype(SafePlaywright,(),{__name__:SafePlaywright},)当运行时请求__class__时只返回这个受限对象。也就是说Playwright ↓ 能拿到自己需要的 __name__但AI 代码 ↓ safe.__class__ ↓ 仍然被 AST Validator 拦截最后形成运行时兼容性 AI 代码静态限制两层防线。八、第四个问题报告生成还有并发竞态前面的测试更多关注“正确性”和“隔离”。继续往下看我又发现报告生成其实也可能发生竞态。AutoPilot 的 Report 既可能被编排器后台流程触发也可能被人工接口触发。如果两个调用同时对同一个execution_id执行查询报告是否存在 ↓ 发现不存在 ↓ 生成 HTML ↓ INSERT两个线程都可能判断不存在然后一起 INSERT。最终就可能撞上数据库唯一约束。这种 Bug 单线程测试很难稳定复现。所以在这轮治理里增加了进程内互斥_REPORT_LOCKthreading.Lock()生成入口使用with_REPORT_LOCK:returnself._generate(execution_id)并补了并发测试用来确认同一个进程内不会同时进入这段关键区。九、但这里还有一个重要边界进程内锁不是分布式锁threading.Lock()解决的是一个 Python 进程 多个线程它不能天然解决Worker 1 Worker 2或者多个进程 多个容器 多副本部署之间的竞态。所以当前方案更准确的表达应该是V1.1 通过进程内互斥控制当前进程内的并发同时依赖数据库唯一约束作为最终边界。如果未来扩展到多副本再考虑幂等写入 数据库唯一约束 execution 粒度锁 / 分布式锁这个边界必须说清楚。十、第五个问题ORM 和数据库 Schema 也可能“各说各话”还有一种问题特别容易被忽略代码里的 Model、初始化 Schema 和未来 Migration不一定天然一致。如果schema.sql ≠ ORM Model ≠ Alembic 预期结构那么短期业务可能完全不报错。但未来一旦开始做 schema diff就会不断出现“这个索引为什么又想创建一次”所以这轮也把已经存在于数据库结构中的部分索引补回 ORM 定义并用 autogenerate 检查结构差异。这类问题看起来不性感但对长期维护非常重要。因为数据库结构最怕的不是复杂而是多个地方各自维护一份“差不多”的真相。十一、到了最后我才真正开始做“风险登记”做到后面我开始意识到一个比 Bug 更危险的问题我很容易忘记到底哪些东西真的验证过哪些东西只是“理论上应该可以”。所以最后不再追求一句“所有问题都解决了。”而是明确区分已修复 已覆盖 环境缺失 / 未验证 已接受 / 设计边界例如真实环境类项目当前仍应该明确区分Docker 实机构建与联调 Real LLM 全链路 MySQL 实机迁移 前端生产 build 多副本并发能验证的就验证。没有环境的就明确写“未验证”。已经知道但当前版本暂时不解决的就记成“设计边界”。这比一句“生产级。”更有价值。十二、1109 个测试全绿到底证明了什么这一轮 V1.1 收口时记录的完整回归结果是1109 passed 2 skipped同时Real Chromium Mock LLM 黄金路径通过 compileallOK这可以证明当前验证范围内的大量断言通过 真实 Chromium 黄金路径跑通但它不能直接推出系统已经没有 Bug更不能直接推出已经全面生产级因为仍然存在明确的验证边界。所以我更愿意把测试结果理解成一组证据。而不是一个结论。十三、这轮收口以后我对“测试开发”的理解又变了一点以前我更容易关注功能有没有通过 覆盖率多少 测试数量多少现在我会继续往后追测试会不会污染测试 Mock 通过以后真实环境呢 并发情况下还成立吗 状态是不是只有一个解释 数据库模型和实际 schema 一致吗 失败以后能不能恢复 没有验证的风险有没有被记录到了这里我觉得测试开发真正做的已经不只是“写测试”。而是在做建立一个能够被验证、被追溯、被解释的工程系统。十四、结尾1109 个测试全绿只是一个起点如果要把 AutoPilot V1.1 这一轮工程治理浓缩成一句话我现在会这样说不是把 Bug 藏起来而是把问题、边界和证据都摆到台面上。一个真正可靠的系统不应该只是“我觉得它应该没问题。”而应该能够回答这里已经验证 这里已经修复 这里有自动化测试 这里有真实环境验证 这里因为环境限制暂时没验证 这里是当前版本接受的设计边界当一个系统开始能清楚回答这些问题时我觉得它才真正开始具备工程系统的样子。而 AutoPilot 的 V1.1只是这条路上的一个阶段。系列文章上一篇《P0-P3 落地实录二进度 100% 却还在自愈AI 测试平台的数据为什么会“自己打自己脸”》本文《P0-P3 落地实录三从测试隔离到并发竞态1109 个测试全绿以后我又发现了什么》下一篇《我把一个 AI 编程 Prompt 推翻了很多轮最后才明白Prompt 不是让 AI 写代码而是防止它绕过架构》项目地址GitHubhttps://github.com/ZipUp-dot/AutoPilot-TestGiteehttps://gitee.com/Mr-6Lawrence/auto-pilot-test关于作者我是 EthanPeng正在做 AutoPilot。我一直比较相信一件事工具应该适应人而不是人适应工具。AutoPilot 想做的事情也很简单让测试人员不需要把大量时间花在重复编写、维护自动化脚本上而是把更多精力放到测试设计、风险识别、质量保障和 AI 代码解释上。而这次测试工程化的过程也让我越来越确定AI 可以帮你生成代码但真正决定一个工具能不能进入真实工程环境的永远是代码之外的那一整套工程能力。欢迎交流。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询