Go 29:单元、HTTP 与数据库测试分别证明什么
上篇用一个真实子进程证明了异常退出后 RUNNING 与 QUEUED 的不同恢复动作。但恢复测试通过,不代表文本计数、HTTP 应答和 SQL 落库都在同一个输入上成立。本篇不另造示例程序,继续检验累计任务服务;完整工程与原始证据、摘要、独立复跑说明放在同名素材目录。
同一条任务走三条证据链
固定输入 "Go one two" 的期望是 Words=3, Bytes=10, Runes=10;"你好 Go" 为 Words=2, Bytes=9, Runes=5。表驱动用 testing.T.Run 分别运行 unit、http、postgresql 三个子测试。后者两个汉字占 6 个 UTF-8 字节,字节数不应按 rune 数填写。stats.Analyze 与状态转移的单元子测试直接操作 TaskStore:Add→Start→Complete,读取终态和值;没有建立 HTTP 连接,也没有打开 SQL 池。若这里失败,先查计数口径与顺序状态逻辑,不必把错误归咎于网络。
HTTP 子测试使用 httptest.NewServer 的真实 loopback 监听,向当前服务的 POST /tasks 发送同一文本,核对 202;再等待服务的 worker 实际退出,GET 得到 200/SUCCEEDED 和三项计数。这验证了 JSON 解码、路由、HTTP 状态和内存执行器的接线。它没有触碰 SQL;服务器能返回 200 不能推断数据库已提交,更不能推断有外部副作用的分析器可安全重试。
SQL 子测试仍从 HTTP 入口提交同一输入,但换成可选 SQL 服务。它既要看 202 和 worker 退出后的 GET,也要调用 sqlStore.Lookup 读取真实 PostgreSQL 表里的原文、SUCCEEDED 与三项计数。这样的断言可识别“HTTP 报成功、SQL 还留在 QUEUED”这一类前几篇出现过的断层;然而它仍没有模拟数据库突然掉电或丢失 Commit 应答。
| 证据层 | 本轮实际走过的边界 | 没有由该层证明的事 |
|---|---|---|
| unit | Go 统计函数、内存任务状态转移 | HTTP 格式、数据库连接与持久化 |
| http | loopback JSON POST/GET、后台 worker 实际退出 | SQL 数据与外部系统副作用 |
| postgresql | loopback HTTP、固定驱动、真实事务与查询 | 跨机器网络、数据库掉电与副作用去重 |
数据库测试不能共享一张未清理的表
GO_LANGUAGE_SQL_DSN 指向一个只在本机实验使用的 PostgreSQL 16.15 管理库。每个 SQL 子测试以独立的 go_task_29_<pid>_<n> 库名执行 CREATE DATABASE,在新库里运行当前服务;t.Cleanup 按逆序先关闭该测试的连接池,执行 DROP DATABASE,然后从 pg_database 查剩余数量。一次正式运行中,两个成功输入及空白反例各创建并删除一库,逐个清理结果为 0;10 次重复之后另用管理连接查询,仍为 0。这避免了前一个子测试遗留的任务 ID 与结果影响下一例。测试账号必须具有建库权限;若环境不给权限,这条集成测试就是未执行,而不应降级为 mock 后继续叫“真实 SQL 测试”。
失败输入同样走真实 SQL-backed HTTP:{"text":" "} 返回 400;独立数据库 SELECT count(*) FROM go_tasks 为 0。直接调用 stats.Analyze(" ") 能算出一个结果,却不会验证 handler 是否在入队前拒绝空白。输入校验发生的位置,是选择测试层的理由。
绿色输出里可能藏着跳过
GO_LANGUAGE_SQL_DSN 缺失时,openIsolatedSQLTestDatabase 调用 t.Skip。专门留了一次命令把 DSN 清除:unit/http 子测试仍 PASS,两个 postgresql 子测试明确 SKIP,整条 go test 命令退出码依然为 0。仅看最后一行 ok 会把数据库从证据里抹掉;只有有真实 PostgreSQL 服务版本、建库/删库日志和 SQL 断言的那次运行,才计入本篇的集成验收。
练习:① 如果 TestTaskInputAcrossUnitHTTPAndPostgreSQL 显示 PASS,但 SQL 子测试全是 SKIP,该称哪个路径已通过?答案:只可称 unit/http 路径通过;数据库部分仍 NOT_RUN。② 若一次 SQL 子测试留下 go_tasks 记录,会影响下一次吗?本篇各建独立数据库,后一测试看不到前库;但清理失败仍应报错,不能靠隔离把遗留资源当作不存在。③ HTTP 202、数据库 SUCCEEDED 和外部收费三者能否合并成同一个成功断言?不能:202 是接收应答,SUCCEEDED 是本表提交结果,外部系统可能在应答丢失时已执行,仍需自己的去重与确认机制。
本机固定 Go 1.27.1、lib/pq v1.12.3、PostgreSQL 16.15;本版 testing.T.Run、T.Cleanup、httptest.NewServer 的 go doc 和 PostgreSQL 16 创建/删除数据库官方页原文、真库单次与 10 次复跑、两包全量测试、race、旧 CLI 正反均有真实命令、stdout/stderr 与退出码,见 writing-plans/go-language/evidence/29/。go test -race 无报告只限跑到的路径。持续负载、远程网络、数据库掉电、外部副作用去重、其他平台、全站构建与发布 NOT_RUN。下一篇让 fuzz 试探解析与状态序列的输入假设,但运行时限内未找到反例不能变成正确性证明。
参考资料
- Go 1.27.1
testing.T.Run、testing.T.Cleanup、httptest.NewServer:固定工具链原始go doc在writing-plans/go-language/evidence/29/doc-*。 - PostgreSQL 16
CREATE DATABASE、DROP DATABASE:官方页本次 HTTP 200 原文、数据库建删与清理检查分别保存在writing-plans/go-language/evidence/29/。






