Published on

Foundry自动化测试方法

Authors

Foundry自动化测试方法

1. 核心断言方法 (Core Assertions)

这些是你验证逻辑是否正确的“尺子”,大部分来自于 forge-std/Test.sol

方法名用途
assertEq(actual, expected)最常用。断言两个值绝对相等。有各种版本适应不同类型(uint, address, string等)。
assertTrue(condition)断言一个条件为真 (true)。
assertFalse(condition)断言一个条件为假 (false)。
assertGt(a, b) / assertLt(a, b)断言 a 大于 (>) 或小于 (<) b
assertGe(a, b) / assertLe(a, b)断言 a 大于等于 (>=) 或小于等于 (<=) b
assertNotEq(a, b)断言两个值不相等,是 assertEq 的反向操作。

  1. 测试结构与流程 (Test Structure & Flow)

这些是组织你测试代码的“骨架”。

名称/方法用途
setUp()特殊函数,在每个 test... 函数运行前都会执行一次,用于准备一个干净的测试环境。
test... (前缀)Foundry 会将所有以 test 开头的函数识别为标准测试用例。测试必须通过
testFail... (前缀)test... 相反,它标识这个测试用例预期会失败 (revert)

  1. VM 作弊码 (Cheatcodes) - Foundry 的超级武器

这是 Foundry 最强大的部分,它允许你“欺骗”以太坊虚拟机(EVM),模拟各种复杂场景。通过 vm 变量来调用。

A. 模拟用户与权限 (Pranking)

作弊码用途
vm.prank(address)核心功能。让下一次外部调用伪装成来自指定的 addressmsg.sender 会变成这个地址。
vm.startPrank(address)开始一段“持续伪装”。之后的所有调用都来自该 address,直到 stopPrank
vm.stopPrank()结束 startPrank 的伪装状态。
vm.deal(address, amount)给指定的 address 发送 amount 数量的 ETH,用于测试需要支付 msg.value 的场景。

B. 预期失败与事件 (Expecting Reverts & Events)

作弊码用途
vm.expectRevert()极其重要。断言下一次外部调用必须失败 (revert)。用于测试 require 语句和各种错误条件。
vm.expectEmit(...)断言下一次外部调用会触发一个特定的事件(Event),并可以检查事件的参数。

expectRevert 使用示例:

Solidity

function testFail_MintToZeroAddress() {
    // 预期下一次调用会因为 "RewardToken__MintToZeroAddress" 错误而失败
    vm.expectRevert(RewardToken.RewardToken__MintToZeroAddress.selector);
    rewardToken.mint(address(0), 100); 
}

C. 操纵区块链状态 (State Manipulation)

作弊码用途
vm.warp(block.timestamp)改变当前的区块时间戳,用于测试与时间相关的逻辑(如锁仓、解冻)。
vm.roll(block.number)改变当前的区块号。

导出到 Google 表格


4. Fuzz 测试 (Fuzz Testing)

这是 Foundry 的另一个强大功能,它会自动生成随机输入来测试你的函数,寻找边缘情况下的漏洞。

  • 如何使用: 只需给你的 test... 函数加上参数即可。

  • 示例:

    Solidity

    // Foundry 会用各种随机的 a 和 b 来调用这个函数
    function test_Addition(uint256 a, uint256 b) public {
        uint256 sum = a + b;
        assertEq(sum, a + b);
    }
    
  • vm.assume(condition): 在 Fuzz 测试中,用于约束随机输入的范围。如果条件不满足,Foundry 会放弃这次测试并生成新的随机数。

    Solidity

    function test_something(uint256 amount) {
        // 假设 amount 必须大于 0 且小于 totalSupply
        vm.assume(amount > 0 && amount < rewardToken.totalSupply());
        // ... 接下来的测试逻辑 ...
    }
    

常用组合拳总结

一个典型的 Foundry 测试流程看起来像这样:

  1. setUp 函数:

    • makeAddr("label") 创建测试账户。

    • vm.deal(...) 给账户发 ETH。

    • vm.prank(...) 模拟成部署者来部署你的合约。

  2. test... 函数 (成功场景):

    • vm.prank(...) 模拟不同用户(所有者、普通用户)的调用。

    • vm.expectEmit(...) 检查事件是否正确触发。

    • assertEq 等断言函数检查最终状态是否正确(如余额、总供应量)。

  3. testFail...test... + vm.expectRevert (失败场景):

    • 模拟恶意用户或错误输入(如转账超过余额、非所有者调用 onlyOwner 函数)。

    • 使用 vm.expectRevert() 来确保合约按预期拒绝了这些非法操作。

  4. Fuzz 测试函数:

    • 为函数添加参数,让 Foundry 帮你找茬。

    • 使用 vm.assume(...) 过滤掉无意义的测试输入。

掌握以上这些方法,你就可以为你的智能合约构建一套非常坚实和全面的自动化测试了。

Reference