Aptos MoveFlow 中的 Move 单元测试编写规范:test 属性、预期失败与覆盖率基线工作流
【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core
本文基于 aptos-core 仓库中 MoveFlow 插件的模板文件 unit_test_rules.md 展开。该模板是 MoveFlow(位于 aptos-move/flow 的 Claude Code 插件,提供 MCP 服务器、插件生成器与编辑 Hook)注入给 AI Agent 的 Move 单元测试语法规则与测试设计准则。读完本文,你将掌握 Move 单元测试的全部核心属性(#[test]、#[expected_failure]、#[test_only])及其变体用法、signer 绑定机制、常用测试工具函数,以及配套的覆盖率基线(baseline)工作流在 MCP 工具中的底层实现。
模板在 MoveFlow 中的定位
unit_test_rules.md 是一个 Tera 模板片段,文件以{# Move unit testing rules #}注释开头,并使用{% if once(name="unit_test_rules") %} ... {% endif %}包裹,保证被多个 Agent/Skill 重复 include 时只渲染一次。根据 CLAUDE.md 的说明,cont/目录下的模板通过 Tera 语法组织为四类:cont/agents/(Agent 指令文件)、cont/skills/(技能定义)、cont/hooks/(事件 Hook 配置)与cont/templates/(被 include 的共享片段)。
该规则片段被以下文件引用,最终进入move-test技能与 Agent 的提示词:
- unit_test_ref.md:单元测试参考材料,通过
{% include "templates/unit_test_rules.md" %}引入本片段,并追加工具使用说明与"生成测试的位置"约束; - unit_test_tasks.md:测试生成工作流的 6 个步骤,被放在 Agent 提示词最前面;
- move-test/SKILL.md 与 move-test.md:分别组装 tasks + ref 两部分内容。
因此,理解本规则片段时,应把它看作"AI 编写 Move 单元测试时必须遵守的语法规则 + 设计规范",而非面向人的泛泛教程。
Move 单元测试语法
测试属性(Test Attributes)
模板给出了完整的属性对照表,这是编写任何 Move 单元测试的入口:
| 属性 | 用途 |
|---|---|
#[test] | 将函数标记为测试 |
#[test(name = @addr)] | 测试函数中 signer 参数绑定到指定地址 |
#[test_only] | 仅编译进测试产物的代码(模块、函数、struct、常量均可) |
#[expected_failure] | 预期会 abort 的测试(任意代码) |
#[expected_failure(abort_code = N)] | 预期以特定 abort code 中止 |
#[expected_failure(abort_code = N, location = mod)] | 预期在特定模块位置中止 |
Signer 参数绑定
模板强调:signer 通过 test 属性绑定,而不是作为函数实参传入。函数签名中声明&signer参数,属性中给出参数名 = @地址的映射:
// 单个 signer #[test(account = @0x1)] fun test_single(account: &signer) { } // 多个 signer #[test(admin = @admin_addr, user = @0x42)] fun test_multi(admin: &signer, user: &signer) { } // 框架 signer,用于时间戳、创建账户等 #[test(aptos_framework = @aptos_framework)] fun test_framework(aptos_framework: &signer) { }其中@admin_addr、@aptos_framework是 Move 包命名地址(named address)的引用写法,在Move.toml中定义。
预期失败(Expected Failures)
模板规定:用#[expected_failure]来验证代码在特定条件下确实正确中止;优先写精确的 abort code 和 location,只有当"失败的种类本身"就是被测行为时才使用无约束的#[expected_failure]。
任意 abort:
#[test] #[expected_failure] fun test_will_abort() { abort 1 }带 abort code(必须精确匹配):
#[test] #[expected_failure(abort_code = E_NOT_AUTHORIZED, location = Self)] fun test_unauthorized() { /* should abort with E_NOT_AUTHORIZED */ }带 location(错误来源在另一个模块时):
#[test] #[expected_failure(abort_code = 26113, location = extensions::table)] fun test_table_error() { /* should abort in table module */ }执行错误(非 abort,而是运行时失败),例如算术错误(溢出、除零)和 vector 越界:
// 算术错误(溢出、除零) #[test] #[expected_failure(arithmetic_error, location = Self)] fun test_overflow() { let _ = 255u8 + 1; } // Vector 越界 #[test] #[expected_failure(vector_error, minor_status = 1, location = Self)] fun test_out_of_bounds() { vector::borrow(&vector::empty<u8>(), 0); }仅限测试的代码(Test-only code)
#[test_only]既可标注整个模块(整个模块只在测试时编译),也可标注普通模块中的函数:
// 测试专用模块(整个模块只在测试时编译) #[test_only] module my_addr::test_helpers { public fun setup(): u64 { 100 } } // 普通模块中的测试专用函数 module my_addr::my_module { #[test_only] public fun init_for_testing(account: &signer, value: u64) { move_to(account, MyResource { value }); } }这与 MCP 工具侧的实现相呼应:package_test.rs 中构造的BuildConfig设置了test_mode: true,即测试编译走独立的 test 模式构建,#[test_only]代码只存在于该模式下。
常用测试工具
模板列出的一组常用辅助操作:
// 从 signer 取地址 use std::signer; let addr = signer::address_of(account); // 创建账户(会在链上注册) use aptos_framework::account; account::create_account_for_test(addr); // 只创建 signer 不注册(轻量) let signer = account::create_signer_for_test(@0x123); // 检查资源是否存在 assert!(exists<MyResource>(addr), E_NOT_FOUND); // 初始化时间戳(调用时间函数前必须执行) use aptos_framework::timestamp; timestamp::set_time_has_started_for_testing(aptos_framework); // 推进时间 timestamp::update_global_time_for_test(1000000); // 微秒 timestamp::update_global_time_for_test_secs(100); // 秒测试设计规范(Test design)
模板给出了四条设计准则与命名约定,这部分直接约束 Agent 生成测试的质量:
- 每个测试只测一种行为,并用到达该行为所需的最少 setup;
- 只要可见性允许就直接调用被测函数;对无法访问的私有函数,通过可观察的公共行为来测,或使用已有的模块内测试模式——不要为了测试而修改生产代码的可见性;
- 注释要解释所检查的行为,尤其是 abort 与边界情况;
- 断言语义结果,而不是断言偶发的实现细节。
命名约定:函数名test_<function>_<scenario>(如test_transfer_insufficient_balance),测试模块名<module>_tests。
常见错误:
RESOURCE_ALREADY_EXISTS:不要对同一资源初始化两次;MISSING_DATA:调用前先确保所需资源存在;- Signer 不匹配:凡是用
signer::address_of()做鉴权检查的操作,必须在 test 属性中把正确的 signer 绑定到预期地址。
生成测试的落位与失败诊断
参考材料 unit_test_ref.md 在本规则之外补充了工作流约束:新生成的测试只允许创建或扩展tests/move_flow/<module>_tests.move,且模块需带#[test_only]标注:
#[test_only] module <package_address>::<module>_tests { use <package_address>::<module>; #[test(account = @0x1)] /// @ai-generated /// Verifies that <function> <behavior>. fun test_<function>_<scenario>(account: &signer) { ... } }对失败测试的诊断分三类:编译错误——修测试本身;setup 或断言写错——修正生成的测试;疑似生产缺陷——把复现用例移到包根目录的bugs/下并记录"预期 vs 实际",除非用户明确要求,否则不改生产代码。整个工作流中只允许编辑tests/move_flow/与bugs/下的生成文件。
配套 MCP 工具的底层实现
规则片段中的用法最终由两个 MCP 工具承载,实现在 package_test.rs:
1.move_package_test——参数为package_path与establish_baseline(源码)。其响应结构TestResponse包含success、baseline_established(仅 baseline 模式)、newly_covered(源码文件路径 → 新增覆盖行号集合,仅正常模式)与output(失败时或无基线时的提示)。关键行为:
- Baseline 模式:只在测试全部通过时才把当前覆盖率存为基线(
save_baseline_coverage_map,源码),确保基线反映一次有效的测试运行;包内没有任何测试时则创建一个空基线。 - 正常模式:与基线对比,返回"基线中未覆盖、现在已覆盖"的行(
compute_newly_covered,源码);测试失败时跳过覆盖率分析,因为覆盖率图可能反映的是部分执行结果。 - 基线文件保存在会话临时目录,文件名用规范化的包路径哈希命名(
baseline_coverage_{hash}.mvcov),使./pkg与绝对路径得到同一哈希,避免碰撞(源码)。 - 测试执行通过
run_tests调用run_move_unit_tests,固定TEST_GAS_LIMIT = 100_000防止死循环挂起,并启用覆盖率统计(compute_coverage: true,源码)。
2.move_package_coverage——参数package_path与可选的function: "module::function"(源码)。指定函数时用make_function_line_filter按函数在源码中的行范围过滤;覆盖率图缺失、或源码变更后重建过包时会自动重跑测试生成新覆盖率。
[unit_test_ref.md](https://link.gitcode.com/i/9e2331ad538627839912345f8d918f36)对这两个工具的使用次序做了明确规定:先以establish_baseline: true建立基线(失败的基线属于既有的证据,不是去编辑无关代码的许可);用move_package_coverage聚焦目标函数或全包未覆盖行;加完测试后再以非 baseline 模式运行,用newly_covered度量新增覆盖。
该工具的端到端行为由 src/tests/move_package_test/ 下的一组测试固化,例如establish_baseline.rs、get_uncovered.rs、newly_covered.rs、coverage_filter_uncovered.rs等;根据 CLAUDE.md 的说明,这些测试以.exp基线文件记录输出,可用UB=1 cargo test -p aptos-move-flow重新生成。
完整的测试生成工作流
unit_test_tasks.md 定义了与上述规则、工具配套的标准 6 步流程,被置于 Agent 提示词最前:
- 建立干净基线:以 baseline 覆盖率模式运行既有测试;不要为了掩盖既有失败而改生产代码或既有测试,应报告它;
- 选择行为用例:阅读被测函数或模块,覆盖有意义的成功路径、不同的 abort、分支与边界值;把未覆盖行当作证据,而不是质量的全部定义;
- 编写隔离的生成测试:新测试只放
tests/move_flow/<module>_tests.move,不改用户手写的测试; - 验证并诊断:修复生成测试中错误的 setup 或预期;若正确的测试暴露了生产 bug,把复现用例保留在
bugs/下并报告,而不是改动断言去凑通过; - 审查覆盖率与冗余:行为不同的用例即使覆盖同一行也保留;只删除行为与覆盖都重复的生成测试;
- 报告结果:列出新增用例、是否全部通过、获得了哪些有效覆盖,以及任何疑似产品缺陷。
小结
unit_test_rules.md 虽然只有百余行,但它构成了 MoveFlow 插件中"AI 写 Move 单元测试"这一能力的规则核心:属性表与 signer 绑定定义了语法边界,#[expected_failure]的精确匹配纪律定义了失败用例的写法,测试设计准则与命名约定定义了质量底线;再结合 unit_test_ref.md 的落位约束、unit_test_tasks.md 的 6 步流程,以及 package_test.rs 中move_package_test/move_package_coverage的基线与覆盖率对比实现,形成了一套从语法、设计到工具链的完整单元测试规范。若要在自己的 Move 包中复用这套规范,可参考其模板结构(cont/templates/+ Tera include),并以cargo install --path aptos-move/flow --locked --profile ci安装 move-flow 后通过其 MCP 工具链驱动测试与覆盖率流程(见 aptos-move/flow/CLAUDE.md)。
【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考