
agentsview 测试方法论编写非循环论证测试的完整实战指南【免费下载链接】agentsviewLocal-first session search, analytics, insights, and token use statistics for coding agents, supporting Claude Code, Codex, and more than 20 other agents.项目地址: https://gitcode.com/GitHub_Trending/ag/agentsview这篇指南基于 agentsview 仓库中的测试技能文档 .agents/skills/testing-without-tautologies/SKILL.md系统讲解如何编写能在生产行为真正损坏时失败的测试从写测试前的质量门提问到断言、mock、镜像断言、后端对等backend parity等十条强制检查再到变异检查mutation check与红旗清单并结合仓库中的真实测试基础设施testDB(t)辅助函数、pgtest对等测试、Makefile 测试目标、Playwright e2e 种子数据工具给出可落地的实操方案。读完后你能够按 agentsview 的测试纪律编写 Go 单元测试、PostgreSQL 集成测试与前端 e2e 测试并具备识别假绿测试的能力。1. 核心思想测试必须在被保护的行为损坏时失败原文档开篇即给出整个方法论的基石测试应该在受保护的行为损坏时失败。一个通过的测试只有在能捕获真实问题时才有价值。在写或改任何测试之前先问自己一个问题什么生产代码改动应该让这个测试失败如果你答不上来就该重新设计这个测试。这类答不上来的测试就是循环论证测试tautological test它验证的是实现本身而不是行为契约。比如用被测的查询构造器去生成期望的 FTS 查询串或者用与被测代码相同的timeutil调用去生成期望时间戳——只要代码自洽测试永远通过即使逻辑完全是错的。本文后续章节的所有检查项都是围绕让这个测试有可能失败这一目标展开的。2. 写测试前的质量门五个必答问题原文档要求在写测试主体代码之前先回答以下五个问题。这是 agentsview 测试流程中设计先行的体现谁消费这个测试的结果优先选择公共 API、HTTP 响应、SSE 事件、持久化行、解析后的会话输出、渲染后的 UI 或 CLI 输出作为断言对象避免断言私有状态。什么例子能证明它使用具体的输入和字面量期望输出。对于解析器要手写 fixture JSONL 和期望的 sessions/messages绝不能运行解析器本身来生成期望值这正是镜像断言的温床。它能捕获什么破坏明确指出是错误分支、缺失副作用、错误参数、边界情况还是契约违反。这是我们拥有的行为吗只在框架、数据库、文件系统边界测试我们的选择不要重新测试依赖库已文档化的机制。能陈述出来吗形式为给定这个前置条件当用户/系统做 X 时可观察行为 Y 发生变化。如果 Y 无法被断言测试就不具备书写的条件。这五个问题与仓库中面向贡献者的测试规范 docs/agents/testing.md 是一脉相承的——后者同样要求为每个新功能和 bug 修复添加单元测试、提交前运行最小相关测试集、保持测试快速且隔离。3. 十条强制检查项详解原文档对每一个新增或修改的测试都要求执行以下检查。下面逐条展开并结合仓库源码给出实证。3.1 断言可观察效果检查返回值、数据库行、HTTP 状态码与响应体、发出的 SSE 事件、解析后的消息内容、渲染后的 DOM 或错误对象。无断言测试仅在失败模式本身就是测试对象时可接受例如这个配置拒绝非法输入即便如此也仍应优先写显式断言。断言风格有硬性规定require.X用于必须中止测试的检查setup 错误、nil 接收者、索引前的长度校验assert.X用于相互独立的检查新测试中禁止if got ! want { t.Fatalf(...) }写法。仓库中的实际做法印证了这一点。internal/db/db_test.go 中的核心辅助函数testDB(t)就是按此风格实现的func testDB(tb testing.TB) *DB { tb.Helper() routeBenchmarkLogs(tb) dir : tb.TempDir() path : filepath.Join(dir, test.db) d, err : openCopiedTestDB(tb, path) require.NoError(tb, err, opening test db) tb.Cleanup(func() { require.NoError(tb, d.Close()) }) return d }它基于t.TempDir()创建隔离的临时 SQLite 数据库从一个模板库复制以保证 schema 完整用require.NoError中止 setup 失败并用tb.Cleanup保证连接关闭——这正是技能文档中优先真实进程内协作者SQLite 用testDB(t)、handler 用httptest、解析器和同步用t.TempDir()fixture 文件的落地。3.2 让 fake 和 mock 足够具体当参数、调用次数、顺序、分支属于契约的一部分时必须校验它们。核心禁令是不要让 fake 接受任意输入——如果代码必须传一个特定值fake 就必须能拒绝别的值。一个来者不拒的 mock 等于没有 mock。3.3 分支替身必须分离不要用同一个 fake handler 去同时覆盖成功、错误、不完整、畸形数据等路径。每个分支应该有独立的 fixture 或 spy这样走错分支就无法碰巧满足期望。3.4 不要 mock 被测对象只 mock 依赖、边界、以及慢的或不确定的协作者。优先使用真实的进程内协作者testDB(t)SQLite、httptestHTTP handler、t.TempDir()fixture 文件解析器与同步逻辑。不要替换被测的解析器、handler、查询、同步步骤或组件本身。不要制造不可能的状态来诱发错误例如为了触发错误而 drop 一列。正确做法是在接缝seam处返回类型化错误并直接对错误到消息的映射做单元测试。3.5 先调查失败再改期望值失败时不要为了通过而翻转期望值。先判断生产代码的改动是否是有意为之然后再更新测试去描述新契约。3.6 避免镜像断言这是全文最核心的一条不要用与测试相同逻辑计算期望值。具体例子包括不要用查询构造器构建期望的 FTS 查询串不要用代码实际调用的同一个timeutil调用生成期望时间戳。使用字面量、手工核对过的 fixture、小型样例或不变量/性质断言。测试逻辑要简单到可以靠肉眼审查。带字面量want字段的表驱动测试table-driven test是首选形态。docs/agents/testing.md同样写明Prefer table-driven tests两处规范互相印证了该项目的测试形态偏好。3.7 不要测试上游功能不要证明net/http路由、mattn/go-sqlite3、FTS5 分词器机制、fsnotify、pgx、Svelte 响应式或 Vite 按其文档正常工作。转而测试你自己的边界契约路由注册、你的代码发出的 SQL、从 HTTP 参数到 DB 查询的取值交接、迁移效果、SSE 载荷形状、错误响应。对于令人意外的上游行为FTS5 的引用规则、WAL 边界情况、watcher 事件顺序围绕你的集成点写一个狭窄的特征化测试characterization test并在测试名或注释中点明所依赖的上游假设。3.8 不要写显然成立的当前代码断言不测试实现现在就是这样写的。跳过纯构造器赋值、getter、平凡转发、常量、纯数据结构的测试例如断言AgentDef条目具有它被声明时的字段。只有当它们做校验、归一化、取默认值、派生、拷贝、权限强制、错误处理、产生副作用或保护兼容性时才测试。优先测试第一个依赖这些字段、消费者可见的结果一个被发现discovered的会话、一个 API 响应、一行渲染出来的 UI。3.9 执行 shell 脚本而不是阅读它shell 脚本测试必须用受控输入运行脚本断言其输出、副作用或退出码。绝不允许读取脚本源码然后断言包含某一行、某个 flag 或某段代码片段。docs/agents/testing.md 的 Shell Tests 小节重复了这条规则说明它是项目级硬性规范。3.10 永远不要写负面存在性测试删除或重构代码时绝不添加断言某函数、方法、文件、导入或符号不存在的测试——无论是 grep 源码树、反射类型还是断言编译失败。删除的证据就是删除本身代码不见了、构建能编译、替代路径的行为测试通过。它还是删掉状态的测试不保护任何东西还会破坏未来对该名称的合法复用并比它所监管的迁移活得更久。如果担心旧路径被悄悄重新接入应转而测试新路径的可观察契约——即如果有人把接线接回去什么行为会坏。移除这类守卫测试是收尾清理的一部分而不是测试覆盖回退。4. 后端对等Backend ParitySQLite 与 PostgreSQL 双侧守护agentsview 同时支持 SQLite 与 PostgreSQL 后端因此原文档专设一节当某个行为要求两个后端表现一致时必须在两侧同时守护——internal/db中的 SQLite 测试与internal/postgres中pgtestbuild tag 之后的 PG 测试应断言同一个可观察契约相同的过滤、排序、聚合和边界情况。原文档的结论很尖锐一个只被单侧测试捕获的对等 bug就是整个测试套件会漏掉的对等 bug。仓库中存在一个教科书级实现internal/activity/parity_pgtest_test.go。该测试验证GetActivityReport在 SQLite、PostgreSQL、DuckDB 三种存储后端上对同一底层数据返回逐字段相同的activity.Report构建一个 SQLite fixture然后通过生产推送路径postgres.Sync/duckdb.Sync推到 PG 与 DuckDB再用相同过滤条件与日期查询三方并深比较。它刻意放在internal/activity的外部测试包activity_test中因为 postgres、duckdb、db 都 import activity内部测试包再 import 三者会形成导入循环而外部测试包单独编译可以无环地导入所有后端。pgtestbuild tag 则让它不进入默认无后端的测试运行。细节处理体现了测试逻辑简单到可审查的原则parityDate固定为过去的一个完整日历日因为当天/未来日期会让各后端独立调用time.Now().UTC()产生微秒级差异成为唯一的非确定性来源paritySchema使用专属 schema避免与其他包共享的agentsviewschema 在并发测试中互相 DROP。这类测试正是第 2 节质量门中什么例子能证明它手写 fixture 字面期望与它能捕获什么破坏任一后端的过滤/聚合/排序偏差两问的完整示范。5. 测试层级选择用能捕获破坏的最窄测试原文档给出按层级选测试的映射全部可以用仓库 Makefile 验证场景测试形态运行方式解析器、同步、db、config、server 逻辑Go 单元测试testDB(t)httptestt.TempDir()fixturemake test需要CGO_ENABLED1与-tags fts5PostgreSQL 行为pgtesttag 的集成测试make test-postgres自动拉起 PG 容器前端逻辑与组件与被测代码同目录的*.test.tsVitest跨越 HTTP/SQLite/SPA 的用户可见工作流frontend/e2e/下的 Playwright specmake e2e种子数据由cmd/testfixture生成Makefile 中的对应目标如下摘自 Makefiletest: pricing-snapshot ensure-embed-dir go test $(GO_TEST_P_FLAG) -tags fts5 ./... -v test-postgres: pricing-snapshot ensure-embed-dir postgres-up TEST_PG_URLpostgres://agentsview_test:agentsview_test_passwordlocalhost:5433/agentsview_test?sslmodedisable \ CGO_ENABLED1 go test -tags fts5,pgtest -v ./internal/postgres/... ./internal/activity/... -count1 -timeout20m e2e: cd frontend npx playwright test几点值得注意的工程细节fts5tag 贯穿全部 Go 测试因为 FTS 分词器扩展是 cgo 编译期绑定的Makefile 顶部还有CGO_CFLAGS指向 go-sqlite3 amalgamation 自带头文件的注释说明测试环境与生产构建共享同一套 SQLite 编译配置。GO_TEST_P ? 4将本地测试进程扇出限制在 4 路避免go test ./...默认按 GOMAXPROCS 并发拉起大量测试二进制。e2e 的种子数据由 cmd/testfixture/main.go 生成该工具用字面量规格project-alpha/small-2到project-delta/xlarge-5500含 subagent、fork、空会话等特殊形态的会话填充一个可预测的数据库例如规格列表中的{project-gamma, empty-0, 0, 0, ...}明确注释也必须被排除在会话列表、统计与分析汇总之外。这正是手写 fixture 字面期望在 e2e 层的体现——e2e 断言的是空会话不出现在列表里这样的消费者可见契约而不是页面没崩。原文档特别要求保持 e2e 测试非循环论证——断言工作流结果、存储状态、渲染 UI 或 API 契约而不是仅断言服务器启动成功或页面未崩溃。与测试时长预算相关的还有make check-timing-budgetsscripts/check-timing-budgets 下的go run ./scripts/check-timing-budgets .它在 lint 与 pre-commit 钩子中强制检查Eventually/Never调用拒绝低于 1 秒的字面预算含零与负时长接受恰好 1 秒及以上的预算例外清单以显式文件、函数、断言、时长、次数的形式维护在scripts/check-timing-budgets/main.go的allowedBudgets中每次新增例外都需具体理由。这从侧面保证了测试快速这一质量门要求是可机械执行的。6. 变异检查Mutation Check完稿前的最后一关原文档要求在收尾前在脑中对生产代码做变异每一种现实可能的变异至少应让一个相关测试失败错误的常量或参数错误的分支 handler缺失的状态变更行未写入、会话未重新同步空/默认返回缺失的副作用没有 SSE 事件、没有 FTS 行你的代码应当察觉的边界上坏掉的 fake私有字段被重命名或重排但行为保持此类变异不应让行为测试失败——如果失败了说明你在测试私有结构而非行为对零值、空值、nil、未授权、畸形输入缺失校验。原文档的收尾判断是如果一个变异都没有测试失败这个测试大概率是循环论证的。 这与第 1 节的核心问题形成闭环质量门在写之前问什么改动会让它失败变异检查在写完之后实际验证这一点。7. 红旗清单如何一眼识别循环论证测试原文档以清单形式总结了循环论证测试的典型体征可作为 code review 时的检查表复用同一个 setup/assertion 对象恒等式保证相等只能靠 panic、error、缺失选择器或服务端崩溃才能失败即使只剩框架/库代码它仍然有意义逐行翻译构造器、getter、setter、mapper 或 wrapper为了覆盖率而存在却不检查副作用、边界或结果把期望值藏在循环、格式化器、构造器或辅助函数后面grep 源文件Go 或 shell断言实现字符串而不是观察行为断言一个已删除的函数、文件或符号保持删除状态。8. 小结把测试纪律写成可执行的流程agentsview 的这套测试方法论在仓库中形成了三层落地规范层技能文档 .agents/skills/testing-without-tautologies/SKILL.md本文主体与贡献者规范 docs/agents/testing.md 保持一致基础设施层testDB(t)internal/db/db_test.go、t.TempDir()fixture、httptest、pgtestbuild tag、e2e 种子工具cmd/testfixture执行层Makefile 的make test/make test-postgres/make e2e/make check-timing-budgets目标以及 golangci-lint 与 pre-commit 钩子。对读者而言可操作的核心心法只有三条写之前回答什么生产改动会让它失败断言消费者可见的可观察效果用字面量期望值收尾前做一次脑内变异确认没有怎么改都绿的测试。这三条做扎实测试套件才真正具备捕获回归的能力。【免费下载链接】agentsviewLocal-first session search, analytics, insights, and token use statistics for coding agents, supporting Claude Code, Codex, and more than 20 other agents.项目地址: https://gitcode.com/GitHub_Trending/ag/agentsview创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考