Foundry 本地合约测试:验证成功写入和失败回滚
链小知 · 进阶 · 核验 · 依官方文档整理
用独立的计数器例子在本地验证写入、revert 和断言失败。完整流程不需要私钥、公共 RPC 或广播交易。
开始前准备
- 已安装可用 Foundry,能够运行 forge;安装方式以官方仓库当前说明为准。
- 使用一个新的本地练习目录,不放入生产私钥或钱包助记词。此教程按官方文档编写,未声称已替读者运行测试。
1. 建立独立练习项目
在空目录中运行 forge init tutorial-counter,再进入该目录。官方仓库说明 Forge 提供构建与测试工具,Anvil 则是本地节点;此处的单元测试不需要额外启动节点。
检查项目已包含 foundry.toml 和 forge-std。若初始化失败,先保存错误并检查目录冲突与依赖获取情况,不继续对生产仓库执行安装或覆盖。
检查结果:练习项目位于新目录,且能够定位 forge-std/Test.sol。
2. 写入可控的测试合约
在 test/BoundedCounter.t.sol 保存下列独立例子。setNumber 只接受小于 10 的值,方便验证成功路径及失败后状态保留。
pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; contract BoundedCounter { uint256 public number; function setNumber(uint256 next) external { require(next < 10, "limit"); number = next; } } contract BoundedCounterTest is Test { function test_SetAndReject() public { BoundedCounter counter = new BoundedCounter(); counter.setNumber(7); assertEq(counter.number(), 7); vm.expectRevert(); counter.setNumber(10); assertEq(counter.number(), 7); } }
这段代码是本教程自编示例;Test、assertEq 与 vm.expectRevert 的使用依据官方 forge-std 文档。示例只检查指定输入与回滚,不代表完成合约审计。
检查结果:文件包含一个合约和一个继承 Test 的测试合约,没有 RPC、私钥或广播设置。
3. 先构建,解决编译问题
运行 forge build。若报 pragma 版本不匹配,检查 foundry.toml 的编译器设置;若 Test.sol 未找到,检查依赖目录和 remapping,而不是删除 import 跳过测试。
构建成功只说明编译通过,尚未证明断言成立。保留构建结果,再进入测试步骤。
检查结果:构建没有编译错误,且测试文件实际纳入项目。
4. 运行指定测试并读出断言含义
运行 forge test --match-contract BoundedCounterTest -vv。测试先设置 7 并读取确认,然后期待设置 10 被拒绝,最后再次确认状态仍为 7。
这分别验证合法输入写入、非法输入 revert,以及失败没有覆盖原状态。看到成功数量时还要确认列出的测试名称确实是 test_SetAndReject;运行了其他默认测试不足以证明本例通过。
检查结果:输出显示本例被执行且通过;保存测试名称与结果。
5. 主动让断言失败,确认测试会发现错误
把最后一条 assertEq 的预期值临时改成 8,重新运行相同命令。此时应该出现失败断言,因为状态仍为 7。
如果改错预期仍全部通过,先检查保存位置、筛选的合约名与实际运行文件。这个检查避免只看测试进程退出而没有验证目标例子。
检查结果:错误预期导致本例失败,输出能定位到断言;不是通过删测试得到成功。
6. 恢复例子并说明验证范围
将最后一条预期恢复为 7,重跑测试并保留结果。整个流程使用本地测试执行,不需要 forge script --broadcast、cast send 或钱包签名。
完成记录注明编译器、测试名称、正常通过和故意失败两组结果。它证明这一小段逻辑在指定条件下的行为,不证明部署地址、复杂资产流或主网交易安全。
检查结果:恢复后通过,并有一次受控失败作为测试确实生效的证据。
怎样确认完成
- 完成合法写入与非法输入回滚两条路径的验证。
- 一次故意改错预期能被测试发现,恢复后再次通过。
- 记录验证范围为本地示例测试,不扩大为生产安全保证。
常见错误与边界
- 只运行 forge build 就声称行为测试通过。
- 测试筛选错对象,默认示例通过而自己的文件没有执行。
- 为了让结果通过删除断言或 expectRevert。
- 在练习项目中加入生产私钥、公共链广播或资产授权。