01 / Workflow
先建立快速反馈循环
一个可复现的测试项目,应该能在本地快速编译、运行测试并定位失败。先把合约、测试、脚本和配置分开,再逐步增加集成场景。
01初始化项目固定编译器和依赖版本。
02写单元测试覆盖函数成功与拒绝路径。
03测试不变量验证余额、权限和状态关系。
04集成验证模拟真实协议和外部调用。
forge init counter-app
forge build
forge test -vvv
forge test --match-test testOnlyOwner02 / Unit tests
先测最小行为,再测组合路径
测试名称应该说明行为和条件。除了“成功后值变成什么”,还要明确未授权调用、零值、边界值和失败时状态是否保持不变。
function testSetUpdatesValue() public {
counter.set(42);
assertEq(counter.value(), 42);
}
function testSetRevertsForStranger() public {
vm.prank(stranger);
vm.expectRevert("not owner");
counter.set(42);
}重要:测试通过只说明测试覆盖到的假设成立,不代表合约没有其他漏洞。
03 / Fuzzing
让输入不再只有一个“好例子”
模糊测试会为参数生成多组输入,适合发现边界、精度、极端数量和状态组合问题。先定义不变量,再让测试反复尝试打破它。
| 不变量 | 需要观察什么 |
|---|---|
| 权限不变 | 非授权账户不能改变管理员或关键参数。 |
| 余额守恒 | 扣除费用后,系统总余额和用户余额关系符合设计。 |
| 失败回滚 | 拒绝操作不会留下部分状态更新或资产变化。 |
04 / Fork testing
在本地模拟真实链上状态
本地 fork 可以让测试读取某个网络的历史状态,再在隔离环境中模拟协议交互。固定区块高度有助于复现问题,但不能替代对当前状态的独立核验。
forge test --fork-url $RPC_URL \
--fork-block-number 19000000 \
-vvv不要把 RPC 密钥、私钥或生产凭证提交到仓库。测试脚本中使用专门的假账户和最小权限。
05 / Delivery
交付前检查清单
不要做:在测试脚本中使用真实私钥,或把未经审计的代码直接部署到真实资产环境。