ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

oh-my-openagent 测试纪律:单进程一次通过、禁止 sleep 与行为断言工程实践

oh-my-openagent 测试纪律:单进程一次通过、禁止 sleep 与行为断言工程实践 人工智能AI Agent代码智能体多智能体MCP ClientsAgent 编排【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址https://gitcode.com/gh_mirrors/oh/oh-my-openagent点击查看免费下载导读oh-my-openagentOmO是一个以mass ulw为入口的图工程graph engineering自动化代理仓库其代码库横跨packages/下数十个核心包、数千个 TypeScript 源文件与海量测试。要在这样一个规模下长期保持测试可信仅靠 CI 流水线远远不够。仓库在 .omo/rules/test-discipline.md 中固化了一整套不可协商NON-NEGOTIABLE的测试纪律所有测试必须能在单进程、一次bun test中全部通过禁止用超时 sleep 等待异步事件禁止用.only/.skip掩盖抖动且针对 prompt / skill / 规则等散文型文件必须断言行为而非断言文本。读完本文你将掌握这套纪律的完整条文、底层原理以及它在仓库测试基建test-setup.ts、bunfig.toml中的真实落地方式可直接迁移到自己的项目。一、总闸门单进程、一次bun test全部通过纪律的开篇定义了整个仓库的验收标准也即质量闸门gate仓库中每一个测试都必须在单进程、一次性bun test中通过——没有隔离标志、没有重试、没有特殊排序。这句话实际上限定了两层含义不允许定向运行一个测试只有加--only、单独开进程或依赖特定执行顺序才能通过那么这个测试本身就是BROKEN。正确做法是修复测试本身而不是溺爱pamper它。排序不可依赖bun test默认会并行、无序地执行测试文件因此任何测试 A 必须在测试 B 之前运行的假设都是脆弱的。这一标准与仓库的测试配置是配套的。根目录 bunfig.toml 中[test]段预加载了全局测试环境[test] preload [./test-env.ts, ./test-setup.ts] pathIgnorePatterns [dist/**, packages/web/**, packages/lsp-tools-mcp/**, packages/lsp-daemon/**, packages/omo-codex/plugin/**, packages/omo-codex/scripts/**, packages/shared-skills/upstreams/**]其中 test-setup.ts 就是单进程跑完整仓库的基石它在进程启动时统一设置默认超时Windows 下 30 秒、POSIX 下 20 秒setDefaultTimeout(process.platform win32 ? 30_000 : 20_000)并在每个beforeEach/afterEach中完成环境快照、mock.restore()、缓存目录清理等全局复位。正因为有这套全局基建才能支撑任意并行顺序下仍稳定通过的硬性要求。二、FLAKY FAILING抖动就是失败纪律对偶尔失败给出了毫不含糊的定义一个测试 10 次里通过 9 次就是10% 的失败率而不是偶尔。偶尔就是BROKEN。这里的关键洞见是在 CI 上同样的代码跑在几十台不同配置的机器上10% 的抖动率意味着几乎每次 CI 都有测试挂掉而挂掉的瞬间没有任何行为回归——抖动测试消耗的是整个团队的注意力与信任。因此仓库不设容忍抖动的灰度区间任何不稳定测试都必须被当作真实缺陷处理。三、测试体内禁止 sleep等够久是一种猜测纪律明确列出测试体test bodies中的禁用项除非时间本身是被测对象例如测Date.now、真实定时器、防抖/节流窗口setTimeout(resolve, N) // 禁用 await new Promise(r setTimeout(r, N)) // 禁用 await sleep(N) // 禁用被禁用的不是 API 本身而是其背后的心智模型——等足够长的时间让 X 发生。理由非常实际足够长enough是猜测CI 机器可能比你的笔记本慢 10 倍也可能快 10 倍用固定 sleep 写的测试在你的机器上通过、在别人的机器上超时而且失败信息只有断言超时没有任何关于到底在等哪个事件的线索。纪律给出的替代方案只有一句先订阅subscribe再触发trigger并用显式超时等待信号signal。四、事件测试三步法先订阅、再触发、显式超时兜底当被测代码会发出事件、触发回调或 resolve 一个 Promise 时纪律规定必须按以下顺序组织测试在触发动作之前注册监听器 / 构造可等待对象。顺序反了 丢失事件 抖动。这是竞态最常见的来源事件在监听器挂上之前就已经发出测试永远等不到。与一个显式超时做赛跑race against an explicit timeout。超时后必须以有用的信息失败例如waited 5s for event X, never fired。严禁静默重试严禁直接放行fall through。超时是熔断器circuit breaker不是同步原语。如果断言逻辑本身依赖超时先触发才能通过那说明测试写错了。仓库中可以看到这一模式的直接体现。在 session-created-callback.test.ts 中测试先构造了记录事件的events: string[]数组和记录onSessionCreated回调再调用manager.launch(...)触发动作最后用一个带 deadline 的waitForEvent显式等待信号async function waitForEvent(events: readonly string[], eventName: string): Promisevoid { const deadlineAt Date.now() 1_000 while (!events.includes(eventName)) { if (Date.now() deadlineAt) { throw new Error(timed out waiting for ${eventName}) } await new Promise((resolve) setTimeout(resolve, 10)) } } await manager.launch({ ... }) // 先订阅回调再触发 await waitForEvent(events, promptAsync) // 显式超时 有用失败信息注意这里的细节轮询间隔10ms是探测节奏而1 秒 deadline 才是真正的断言边界超时抛出的错误信息明确说出在等哪个事件这正是纪律要求的有用失败信息。轮询等待与盲目 sleep 固定时长的本质区别在于前者一收到信号就立刻继续快机器上测试跑得快慢机器上也不会因为固定等待而误判。五、禁止隔离拐杖状态泄漏必须被找到而不是被绕过纪律要求测试在单次bun test运行中的任意并行顺序下都必须工作无论涉及多少个 mock。为此明确禁用了三类隔离拐杖.only/.skip掩盖抖动测试把测试放进独立进程来修复状态泄漏——规则特别指出仓库脚本script/run-ci-tests.ts已经会为使用mock.module()的文件做自动隔离不得为了掩盖真实的跨测试 bug 而往这个名单里塞文件重排describe/it块来掩盖跨测试污染以及依赖测试 A 先于测试 B 运行。纪律对跨测试污染的定性是状态泄漏就是状态泄漏。正确的三类出路是找到泄漏点本身在beforeEach中复位状态如果该状态是全局共享的就把复位逻辑加到 test-setup.ts 里在模块边界做 mockmock.module而不是去修改其他测试也会读取的全局变量。仓库的 test-setup.ts 正是这一条的工程化落地。它做了几件典型的全局复位在模块加载时用mkdtempSync创建一次性密闭 HOMEHERMETIC_HOME指向process.env.HOME与USERPROFILE避免发现型测试受开发者本机真实技能/配置目录影响beforeEach中快照process.env、清理 OMO 缓存目录、复位 Claude 会话状态、模型回退状态、连接 Provider 缓存、异步门预留等resetClaudeSessionState()、resetModelFallbackState()、resetConnectedProvidersCache()等afterEach中恢复环境快照、还原Date.now与各类定时器快照dateNowSnapshot、setTimeoutSnapshot等、执行mock.restore()与restoreModuleMocks()完成全局 mock 生命周期管理。这套机制确保单进程、任意顺序下每个测试文件看到的都是一个干净的环境——这正是不靠隔离拐杖靠真正复位的仓库级示范。六、Prompt 测试断言行为不要断言文本纪律用很重的篇幅处理一类仓库特有的测试对象prompt、skillSKILL.md、rule 以及任何 markdown/指令文件。核心判断是这类文件是散文PROSE。它的措辞不是契约模型会读它、人会改它、每个迭代都会变。绝对不要写一个断言散文说了什么的测试。被点名的禁用模式每一种都是在守一个 diff而不是在守行为expect(prompt).toContain(You are Sisyphus) // 短语存在性 pin expect(skill).not.toContain(old wording) // 短语缺失 / 旧措辞守卫 expect(prompt).toMatchSnapshot() // 对散文做快照 expect(prompt).toBe(EXPECTED_PROMPT) // 全文 pin expect(wordCount(workflow)).toBeLessThanOrEqual(3930) // 词数 / 字符数 / 行数上限 expect(md.match(/some phrase/g)?.length).toBe(1) // 短语出现次数为什么这些全部被禁纪律给出的论证非常完整措辞一变测试就挂然后下一个工程师为了让 CI 变绿会机械地修改断言去匹配新文本却根本没搞清楚这个断言原本在守护什么。于是测试没有守护任何东西反而阻塞每一次合理的 prompt 编辑——直到有人bump掉那个被 pin 住的字符串或数字。这种测试是假装覆盖率pretend-coverage。七、按谁来消费这个文件决定怎么写测试纪律给出了一个可操作的决策框架决定权在于文件的消费者是谁而不是文件内容本身。分为三种情形情形一有机器在消费其中的值。例如解析器读 frontmatter 字段、hook 用 grep 找哨兵 token、校验器运行文档里的 JSON 示例、运行时按文档中记载的工具名分发。此时就测那个消费点解析字段并断言取值或让真实的消费者跑一遍这个文件——而不是去断言周边的散文。情形二同一文件以两份拷贝交付且必须保持一致。例如共享源码 打包后的副本。此时用一次两个真实产物的相等断言来守护漂移expect(packaged).toBe(source)绝不要用一长串短语 grep 列表来模拟这种一致性。情形三纯散文改动没有任何机器消费者。例如改写指引、收紧措辞、补充示例。此时没有行为缝隙behavioral seam可测就不要写自动化测试。守护手段是 code review 人工通读QA-by-read而不是 grep。一个绿灯的文本 pin 在这里就是假装覆盖率跳过这个测试才是正确且诚实的选择。纪律特别注明这是唯一一处每个改动都需要一个红灯测试不适用的情况——请在 PR 里说明而不是硬造一个 pin。八、存在行为缝隙时必须断言代码真正执行的这条分支当行为缝隙确实存在时纪律要求断言代码强制执行的那个条件并且要锚定运行时同样在使用的稳定 token而不是某个句子。仓库给出的四类必测场景当teamMode.enabled true时builder 必须包含team_send_message工具 → 测这个分支本身用运行时也使用的稳定 token 做键当verbose false时debug 指令必须缺席 → 测否定分支API 密钥绝不允许出现在 system message 中 → 测脱敏/redaction逻辑Skill X 请求时加载、不请求时缺席 → 同时测包含与排除两个方向。一句话总结整节测什么东西会破坏行为永远不要测什么东西只会破坏 diff。九、委派写测试只给行为不给断言字符串纪律的最后一条针对把写测试交给子代理subagent的协作场景规则非常严格把测试写作委派给子代理时给它测试必须区分的行为例如override 优先级被破坏时应当失败绝不能给它现成的断言字符串、prompt 片段、期望的通过/断言数量或当前测试已经在用的 marker。理由是深刻且反直觉的一个被规定死的机制如果本身是错的子代理会忠实地把它实现出来——缺陷带着绿灯的测试套件一起上线而这恰恰是这条规则要防止的事故。规则还提醒附近那些toContain(sentence)的测试是既有的违规不是可以效仿的惯例。十、把纪律沉淀成基建仓库的落地证据整套纪律并非停留在规则文件里而是已经物化在仓库的测试基建中可作为对照实现.omo/rules/test-discipline.md规则本体其 frontmatter 中的globs定义了生效范围——所有**/*.test.ts、**/*.spec.ts、src/testing/**/*.ts、test-setup.ts等即读或编辑任何测试文件时都会触发该规则test-setup.ts单进程运行的全局复位层包含密闭 HOME、环境快照/恢复、定时器与Date.now还原、全局 mock 生命周期installModuleMockLifecycle、每次测试前的缓存与状态清理bunfig.tomlbun test的全局配置通过preload注入上述复位层并通过pathIgnorePatterns排除无需测试的构建产物与第三方目录session-created-callback.test.ts先订阅、再触发、显式 deadline 等待事件的标准写法范例mock.module()的广泛使用在packages/omo-opencode/src下cli-installer、team-mode、tmux-subagent、claude-code-hooks、atlas等 30 个测试文件都在模块边界使用mock.module()这与模块边界 mock而非污染全局的纪律一致这些 mock 由test-setup.ts的restoreModuleMocks()统一回收保证并行顺序下的互不干扰。结语测试纪律的本质是信任管理把test-discipline.md从头读到尾会看到一条清晰的主线每一条禁令都在对抗同一种风险——测试套件变成绿灯但不可信。单进程一次通过杜绝了我的机器上能过的侥幸禁止 sleep 与强制显式超时把等不到变成报错说清在等什么禁止隔离拐杖逼着团队真正修复状态泄漏禁止文本 pin让 prompt 类文件回归行为契约而非措辞契约。这套纪律的最终产物不是更严格的 CI而是每个开发者都可以信任的绿灯——绿灯亮起就代表行为没坏而任何一次红灯都对应一个真实存在、值得修复的问题。赞分享人工智能AI Agent代码智能体多智能体MCP ClientsAgent 编排【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址https://gitcode.com/gh_mirrors/oh/oh-my-openagent点击查看免费下载相关推荐oh-my-openagent ulw-loop 代码质量门禁实战纯 LOC 预算、禁止构造审计与 Given/When/Then 测试纪律oh my openagent ulw loop 代码质量门禁实战纯 LOC 预算、禁止构造审计与 Given/When/Then 测试纪律 导读 本文基于人工智能AI Agent代码智能体多智能体MCP ClientsAgent 编排在 oh-my-openagent 中通过 allowed_comment_prefixes 消除 comment-checker 误报原理、配置与测试实践在 oh my openagent 中通过 allowed_comment_prefixes 消除 comment checker 误报原理、配置与测试实践人工智能AI Agent代码智能体多智能体MCP ClientsAgent 编排draw.io 绘制电气工程电路图的免费完整指南ECE 电路库使用入门draw.io 绘制电气工程电路图的免费完整指南ECE 电路库使用入门 Draw.io ECE 是一个面向 draw.io 的免费电路图绘制库把电阻、电容、人工智能AI Agent代码智能体多智能体MCP ClientsAgent 编排创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表