乐于分享
好东西不私藏

AI 软件开发实战教程(八):先让项目能稳定地跑起来

AI 软件开发实战教程(八):先让项目能稳定地跑起来

“AI 软件开发实战教程”系列第 8 篇:用第一个 TDD 节点建立可复现工程、统一验证命令和诚实的环境边界,为后续业务开发准备一条不会越走越乱的跑道。

上一篇把邻行拆成了十个纵向开发任务,但真正开始写邀请注册以前,计划里还有一个看起来不那么像产品功能的 V0:工程骨架与验证契约。

它没有漂亮页面,也没有匹配算法。它只回答几件基础问题:

  • 新环境怎样安装项目;
  • Web、后台任务和数据库怎样启动;
  • 修改代码后最少要运行哪些检查;
  • 配置错误能不能在启动时暴露;
  • 时间和第三方提醒怎样在测试中稳定替换;
  • 浏览器测试究竟验证了什么,还有什么没有验证。

这些问题如果不先回答,后面的 TDD 很容易变成“在我的机器上偶尔能跑”。

1. 工程骨架不是先写一堆空目录

有一种常见的“搭架子”方式,是一次创建所有计划中的模块、模型和接口,每个文件先留一个空类。目录看起来很完整,实际上没有一条命令能证明这些东西能够协同运行。

邻行的 V0 只实现后续任务现在就需要的基础能力:

Python 3.12 + Django 5.2 LTS  ├─ 分层设置与生产配置校验  ├─ 第一条自定义用户迁移  ├─ 存活与就绪健康检查  ├─ 可注入时钟  ├─ 假提醒渠道  ├─ 空通知 Worker  └─ pytest / Ruff / mypy / Playwright

社区、出行、匹配、交换等模块没有为了“结构完整”提前创建。它们会在对应纵向任务中,由真实失败测试推动出现。

2. 第一个红灯必须因正确原因失败

TDD 的第一步是 RED,也就是先看到测试失败。

这不等于故意写错断言。红灯必须说明目标能力确实还不存在。

V0 最先写了几类测试:

  • 生产环境缺少密钥、允许域名或数据库地址时拒绝启动;
  • 健康端点返回明确状态;
  • 项目从第一条迁移使用自定义用户模型;
  • 固定时钟拒绝没有时区的信息;
  • 假提醒渠道只记录消息,不访问真实网络。

第一次运行得到的核心错误是:

No module named linxing.settings

这是有效红灯,因为项目配置此时确实不存在。它不是数据库没开、依赖没装或测试拼错名字造成的偶然失败。

随后只创建满足这些测试的最小 Django 工程。测试变绿以后,再整理重复配置和模块边界。

3. 版本选择要服从可验证环境

架构初稿曾把 Python 3.13 作为目标,但真正进入 V0 后发现当前开发机只有 Python 3.12.3。

这时有两个选择:

  1. 文档继续写 3.13,代码却偷偷用 3.12;
  2. 把事实源调整为实际可执行、同时受 Django 5.2 LTS 支持的 3.12。

邻行选择了第二种。

版本规划不是愿望清单。教程如果宣称“已经验证 Python 3.13”,却从未在该版本运行测试,后续读者会把一个假设当成证据。

项目仍允许 Python 3.12 到 3.13,但当前检查点只声称 3.12.3 已经实际运行。

4. 把常用验证变成统一入口

随着工具增加,开发者很容易记住不同版本的命令:有人只跑单元测试,有人忘了格式检查,有人直接跳过浏览器。

V0 用 Makefile 和 HARNESS.md 固化了几个入口:

make quick  格式与静态规则 + 不依赖 PostgreSQL/浏览器的快速测试make test  非浏览器测试 + 覆盖率门槛make check  Ruff + 格式 + mypy + 迁移漂移 + Django 系统检查make e2e-chromium  当前机器可执行的 Chromium 浏览器验证make e2e  Chromium + WebKit 完整浏览器验证make bugfix TEST=...  针对一个失败场景的最小复现

HARNESS.md 不只是复制命令,还要记录运行条件。例如 make e2e 当前需要主机额外安装 WebKit 系统库,涉及行锁的并发测试需要真实 PostgreSQL。

统一入口的价值不是少敲几个字符,而是让人和 AI 对“快速验证”“完整验证”说的是同一件事。

5. 覆盖率门槛也会暴露设计问题

最初的快速测试全部通过,但运行带覆盖率的测试时失败了:

15 passedcoverage: 70.69%required: 80%

这次失败不能简单地把门槛从 80% 改成 70%。先看缺失行,发现两类问题:

  • ASGI、WSGI 和不同环境设置等启动壳被算入业务覆盖;
  • 数据库地址解析和通知 Worker 的可观察行为缺少测试。

修正时分别处理:

  • 从覆盖统计排除没有业务分支的启动配置;
  • 增加 PostgreSQL 地址解析、错误协议、缺少主机、凭据解码测试;
  • 增加 Worker 单次空轮询测试;
  • 增加低多样性生产密钥的拒绝测试。

最终结果是:

21 tests passedbranch coverage: 94.77%

覆盖率不是质量分数,但一次失败帮助我们发现了真正缺少验证的配置路径。正确做法是解释数字背后的代码,而不是只追求仪表盘变绿。

6. 生产配置应该尽早失败

开发环境常常会提供宽松默认值,生产环境不能这样做。

邻行的生产配置要求以下信息显式存在:

  • Django 密钥;
  • 允许域名与 CSRF 可信来源;
  • PostgreSQL 地址;
  • 敏感字段密钥及当前版本;
  • 网站公开地址。

缺少任何一项都会在设置加载阶段报错。密钥不仅要求长度至少 50 个字符,还拒绝由同一个字符重复组成的低多样性值。

在一组临时、非真实凭据下执行 Django 的生产部署检查,结果为零警告。

这里验证的是配置规则,不代表服务器已经部署,更不代表示例密钥可以用于生产。

7. 假时钟与假渠道为什么现在就需要

邻行的核心规则大量依赖时间:默认 15 分钟窗口、截止、过期、反馈期限和提醒摘要。

如果业务代码直接到处读取当前系统时间,测试只能碰运气或真实等待。V0 因此先定义可注入时钟:生产使用系统时钟,测试使用固定的带时区时刻。

第三方提醒也一样。

Gate A 已经证明喵提醒真实接口可用,但常规测试不应该重复访问它,更不能把真实喵码放进仓库。假提醒渠道只接收相同结构的消息并记录结果,让业务任务可以测试“应该发给谁、生成什么事件”,而不是真的发微信。

真实 HTTP 适配器和失败重试会在后续提醒任务实现。

8. 浏览器通过不等于所有浏览器都通过

V0 创建了最小 Playwright 冒烟测试:启动 Django 测试服务器,用真实浏览器打开主页,确认应用可访问。

Chromium 测试通过:

1 passed

但当前机器的 WebKit 缺少系统动态库,安装这些库需要管理员权限。项目没有把 Chromium 结果复制成“两个浏览器都通过”,而是在 HARNESS.md 和节点检查点中保留限制。

同理,当前机器没有 Docker 和 PostgreSQL 服务。SQLite 可以让大多数快速测试运行,却不能证明 PostgreSQL 行锁和并发行为。

因此这两个结论必须分开:

工程已经具备 WebKit 与 PostgreSQL 的运行契约当前环境已经完成 WebKit 与 PostgreSQL 验证

这种区分会一直保留到有足够环境证据为止。

9. 让仓库自己告诉下一位开发者怎样工作

V0 最后生成并校正了三份仓库级说明:

  • AGENTS.md
    :AI 修改代码时必须遵守的架构、安全和隐私边界;
  • ARCHITECTURE.md
    :快速查看模块关系与核心调用链;
  • HARNESS.md
    :已经实际确认的安装、运行、快速检查和完整验证命令。

项目根 README、开发看板、任务详情和节点检查点也同步到同一个事实:V0 已完成,下一项是 K1。

如果只提交代码而不更新这些入口,下一次会话仍可能把 V0 当作未开始,或者重新猜一套命令。节点收尾的作用,就是让仓库状态不依赖聊天记录。

10. 本节点交付了什么

V0 的最终证据是:

  • 21 个快速与完整非浏览器测试通过;
  • 分支覆盖率 94.77%;
  • Ruff、格式检查、mypy strict、迁移漂移检查通过;
  • Django 普通和生产部署系统检查通过;
  • Chromium 真实浏览器冒烟通过;
  • WebKit 与 PostgreSQL 的未验证原因被明确记录;
  • 仓库没有真实 .env、微信号、喵码或生产凭据;
  • 所有结果只形成本地提交,没有推送。

它仍然没有实现任何 AC-01 到 AC-56 的业务验收场景。这不是遗漏,而是 V0 的边界。

11. 写在最后

测试驱动开发的第一步,不一定是某个业务按钮。

对于一个从文档开始的新项目,更可靠的第一步是先证明:环境能够复现,配置错误能够被看见,测试可以稳定替换时间和外部服务,所有人使用同一套验证入口。

这条跑道建好以后,下一篇才进入第一个真实用户闭环:社区邀请码、用户名密码登录,以及不会在普通页面和日志中泄露的微信号与喵码。

那时 TDD 要证明的不再只是“页面能打开”,而是邀请资格、会话权限、敏感资料加密和失败恢复都符合已经批准的产品规则。

12. 关键代码与操作

下面是生产配置失败测试的简化摘录。它只展示验证方法,省略了项目内部辅助函数和导入:

deftest_production_config_rejects_short_secret_key():    environment = valid_environment()    environment["DJANGO_SECRET_KEY"] = "too-short"with pytest.raises(ImproperlyConfigured, match="at least 50"):        load_production_config(environment)

验证命令:make bugfix TEST=tests/test_config.py::test_production_config_rejects_short_secret_key

这条测试的重点不是某个具体密钥,而是证明生产配置错误会在进程启动前被明确拒绝。

13. 本篇验证摘要

  • 项目提供统一的环境安装、快速检查、完整测试和浏览器测试入口;
  • 开发配置可以直接启动,生产配置缺少数据库、密钥或站点地址时会尽早失败;
  • 健康检查分别验证进程存活和数据库可用性;
  • 自动测试可以替换时钟和第三方渠道,不依赖真实提醒服务;
  • 当前环境未完成 PostgreSQL 并发和 WebKit 系统依赖验证,这两项继续作为明确缺口。

14. 附录:相关工具与仓库

14.1 gstack

仓库:garrytan/gstack 地址:https://github.com/garrytan/gstack

14.2 dev-harness

仓库:Dev-Wiki/dev-harness 地址:https://github.com/Dev-Wiki/dev-harness

14.3 UI UX Pro Max Skill

仓库:nextlevelbuilder/ui-ux-pro-max-skill 地址:https://github.com/nextlevelbuilder/ui-ux-pro-max-skill