模糊测试(Fuzz Testing)
Forge 支持基于属性的测试。
基于属性的测试是一种测试一般行为而不是孤立场景的方法。
让我们通过编写单元测试来检查这意味着什么,找到我们正在测试的一般属性,并将其转换为基于属性的测试:
pragma solidity 0.8.10;
import "forge-std/Test.sol";
contract Safe {
receive() external payable {}
function withdraw() external {
payable(msg.sender).transfer(address(this).balance);
}
}
contract SafeTest is Test {
Safe safe;
// Needed so the test contract itself can receive ether
// when withdrawing
receive() external payable {}
function setUp() public {
safe = new Safe();
}
function testWithdraw() public {
payable(address(safe)).transfer(1 ether);
uint256 preBalance = address(this).balance;
safe.withdraw();
uint256 postBalance = address(this).balance;
assertEq(preBalance + 1 ether, postBalance);
}
}
运行测试,我们看到它通过了:
$ forge test
Compiling 6 files with 0.8.10
Solc 0.8.10 finished in 3.78s
Compiler run successful
Running 1 test for test/Safe.t.sol:SafeTest
[PASS] testWithdraw() (gas: 19462)
Test result: ok. 1 passed; 0 failed; finished in 873.70µs
这个单元测试_确实测试_我们可以从我们的保险箱(Safe 合约)中取出以太币。 但是,谁能说它适用于所有金额,而不仅仅是 1 个以太币?
这里的一般性质是:给定一个安全的余额,当我们提取时,我们应该得到保险箱里的所有的资金。
Forge 将运行任何至少采用一个参数的测试作为基于属性的测试,所以让我们重写:
contract SafeTest is Test {
// ...
function testWithdraw(uint256 amount) public {
payable(address(safe)).transfer(amount);
uint256 preBalance = address(this).balance;
safe.withdraw();
uint256 postBalance = address(this).balance;
assertEq(preBalance + amount, postBalance);
}
}
如果我们现在运行测试,我们可以看到 Forge 运行基于属性的测试,但它因 amount 的高值而失败:
$ forge test
Compiling 1 files with 0.8.10
Solc 0.8.10 finished in 1.69s
Compiler run successful
Running 1 test for test/Safe.t.sol:SafeTest
[FAIL. Reason: EvmError: Revert Counterexample: calldata=0x215a2f200000000000000000000000000000000000000001000000000000000000000000, args=[79228162514264337593543950336]] testWithdraw(uint256) (runs: 47, μ: 19554, ~: 19554)
Test result: FAILED. 0 passed; 1 failed; finished in 8.75ms
给测试合约的默认以太币数量是 2**96 wei
(在 DappTools 中),所以我们必须将 amount 类型限制为 uint96
,以确保我们不会尝试发送超过uint96
的值, 我们使用参数:
function testWithdraw(uint96 amount) public {
现在它通过了:
$ forge test
Compiling 1 files with 0.8.10
Solc 0.8.10 finished in 1.67s
Compiler run successful
Running 1 test for test/Safe.t.sol:SafeTest
[PASS] testWithdraw(uint96) (runs: 256, μ: 19078, ~: 19654)
Test result: ok. 1 passed; 0 failed; finished in 19.56ms
您可能希望使用 assume
作弊码排除某些情况。 在这些情况下,模糊器 fuzzer 将丢弃输入并开始运行新的模糊测试:
function testFuzz_Withdraw(uint96 amount) public {
vm.assume(amount > 0.1 ether);
// snip
}
有多种方法可以运行基于属性的测试,特别是参数测试和模糊测试。 Forge 仅支持模糊测试。
解读结果
您可能已经注意到,与单元测试相比,模糊测试的总结略有不同:
- "runs" 是指模糊器 fuzzer 测试的场景数量。 默认情况下,模糊器 fuzzer 将生成 256 个场景,但用户可以设置此参数以及其他测试执行参数。有关模糊测试器配置详细信息,请参阅
这里
。 - “μ”(希腊字母 mu)是所有模糊运行中使用的平均 Gas
- “~”(波浪号)是所有模糊运行中使用的中值 Gas
配置模糊测试执行
模糊测试执行受用户通过 Forge 配置原语控制的参数的约束。配置可以全局应用,也可以基于每个测试进行应用。有关此主题的详细信息,请参阅
📚 全局配置
和 📚 内联配置
。