最近用 Cursor 生成了一批单测代码,合计 1.1w 行代码,最开始生成的效果并不好,经过一番调教,目前我所维护的仓库已经实现某种半自动化的 UT 生成能力:在我并不理解代码逻辑的情况下,仅需一行 Prompt ,即可为 Monorepo 中某个 Package 所有源码批量生成单测的效果:
图片
图片
这里面 Cursor 逐次做了几件事情:
- 根据包名找到源码目录;
- 扫描源码目录,确定需要生成单测的源码文件,并记录到 .cursor/task.md 文件中;
图片
- 针对 .cursor/task.md 标记的每一个任务,根据 .cursor/rules/instruct-ut.mdc 指令迭代生成单测代码,直至测试通过且覆盖率达标。
这是怎么做到的呢?PE!纯粹的PE,没有增加任何一行代码!
Prompt 解析
首先,Cursor 支持多文件编辑,支持仓库索引,支持配置 .cursorrules 等特性,因此其本身就有能力批量完成编程任务,但在过往的测试中,批量生成单测的效果并不好,遇到过不少问题,主要可归结为两个点:
- Package 的文件可能很多,Cursor Composer 经常生成一部分文件后就会退出生成过程,只能部分完成任务;
- 我所维护的是一个 Monorepo 仓库,体积很大,合计 300+ Package,200w 行代码,代码的嵌套层次很深,模块依赖关系非常复杂,并且大量使用 TS Alias 等特性,导致单测的复杂度也相应增加了许多;
针对第二点,实测只需补充若干 Prompt ,用于明确单测代码规范、技术栈等关键上下文信息即可(.cursor/rules/spec-for-ut.mdc):
难点在于第一点:如何持续性地生成代码?这里取了个巧,让 Cursor 在将任务进度记录到外部文件中(.cursor/rules/instruct-ut.mdc):
之后,Cursor 每次生成单测时都会先到 tasks.md 文件中查找任务列表,恢复上次执行进度。
效果
在上述 Cursor Rules 基础上,后续的交互就简单许多了,只需使用下述 Prompt 即可:
至此,至少从“量”上,Cursor 已经能帮助生成比较完整的单测代码了,并且大部分简单代码都能一次性通过。但对于复杂场景、复杂组件,生成的代码通常会存在一些问题,没法直接跑通,例如:
图片
图片
接下来就需要人工介入修复这些疑难杂症了,有一些小技巧:
- 使用 测试框架(vitest/jest 都有提供) 的 [only](https://vitest.dev/api/#describe-only) 接口配合 Filter 能力,只跑存在问题的用例,降低信息噪音;
- 可以使用terminal 右上角的 Add to Composer 按钮,让 LLM 继续帮你解决问题;
图片
- 其次,绝大部分问题都出在 Mock 上面,例如下图所示,错误看起来是 ESM 与 CommonJS 互相调用的问题,实则可以通过 Mock 解决,因此需要有意识 Mock 掉所有下游模块调用、环境变量调用等(LLM 在这方面做的并不是很好,还是需要比较多人工介入);
图片
最后
为免遗漏,最后在总结几个必要条件:
- 安装 Cursor,尽可能充上会员;
- 开启 Codebase Indexing;
- 打开 Cursor Agent Yolo 模式,它能自动调用各类 cli 工具,验证生成的代码是否符合预期,比如自动调用 lint 检查代码风格,ts 检查类型合规,vitest 检测单测是否通过等;
图片
- 把上述两段 Rules 加进 .cursor/rules 中。
其次,面对复杂代码时,LLM 通常无法顺利生成对应单测,甚至在人类智能介入的情况下,成本依然很高,因此更建议尽可能保持源码的简洁性,遵循一些最佳实践规则。