Forge 测试合约
本章讲解如何编写测试用例,以及如何使用 forge
对合约进行测试。
使用 Solidity
编写的智能合约,在部署到主网之前,都需要经过严格的测试。
我们可以在 Foundry
中使用 Solidity
语言编写测试用例,再运行 forge
命令对目标合约进行自动化测试。
被测试合约
默认方式下创建的 Foundry
项目,它的目录 src
中包含了一个被测试的例子合约 Counter.sol。
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; contract Counter { uint256 public number; function setNumber(uint256 newNumber) public { number = newNumber; } function increment() public { number++; } }
合约 Counter 包含两个函数:
- 函数 setNumber 用来设置状态变量 number 的值。
- 函数 increment 将状态变量 number 的值加 1。
测试用例合约
测试用例(Test Case)是软件测试中的基本概念。它是为了验证合约的某项功能是否按照需求正常工作,精心设计的一组输入数据、执行条件以及预期结果。
简单来说,测试用例就是一系列的步骤和检查点,用来帮助测试人员判断软件的特定部分是否正常工作。
在 Foundry
项目的测试用例中,如果测试的功能 revert
,就表示测试失败;如果测试的结果符合预期,就表示测试通过。
默认方式下创建的 Foundry
项目,它的目录 test
中包含了一个测试用例合约 Counter.t.sol,专门用来测试 Counter.sol。
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {Counter} from "../src/Counter.sol"; contract CounterTest is Test { Counter public counter; function setUp() public { counter = new Counter(); // 创建被测试合约 counter counter.setNumber(0); // 调用被测试合约的函数 setNumber,设置初值 } function test_Increment() public { counter.increment(); // 调用被测试合约的函数 increment assertEq(counter.number(), 1); // 判断被测试合约中 number 的值是否等于 1 } function test_SetNumber(uint256 x) public { counter.setNumber(x); // 调用被测试合约的函数 setNumber assertEq(counter.number(), x); // 判断被测试合约中 number 的值是否等于 1 } }
测试合约需要继承 标准测试合约 Test,它定义在 forge-std/Test.sol 文件中,具有日志记录和断言功能。
我们需要在测试用例合约的开头通过 import
引入。
在这个测试用例合约 CounterTest 中,包含了 3 个函数:
- setUp
它在每个测试用例运行之前被调用。这个函数通常用来创建要测试的合约,并做一些初始化工作。函数名称 setUp 是一个关键字,不能定义成其它名字,否则就不会被自动调用。
- test_Increment
这是第一个测试用例,用来测试 Increment 函数的功能。测试用例的名字必须以关键字 test
开头。
先调用 counter 合约的 increment 函数,使得合约中的状态变量 number 加 1。
最后判断调用结果是否符合预期。其中,assertEq
是关键字,它是一个断言,用来判断两个参数是否相等,不相等就会 revert
;两者相等则表示测试通过。
- test_SetNumber
这是第二个测试用例,用来测试 SetNumber 函数的功能。测试用例的名字必须以关键字 test
开头。
先调用 counter 合约的 SetNumber 函数,设置合约中状态变量 number 的值为 x。
最后判断调用结果是否符合预期。
运行测试
我们编写测试用例后,就可以进行自动化测试了。
在命令行中输入测试命令:
forge test
输出测试结果:
[⠔] Compiling... [⠒] Compiling 27 files with 0.8.13 [⠘] Solc 0.8.13 finished in 2.06s Compiler run successful! Ran 2 tests for test/Counter.t.sol:CounterTest [PASS] test_Increment() (gas: 31325) [PASS] test_SetNumber(uint256) (runs: 256, μ: 30487, ~: 31265) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 24.39ms (16.52ms CPU time) Ran 1 test suite in 313.80ms (24.39ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)
我们可以看到输出的结果中:2 tests passed。这表示合约通过了测试。
如果我们修改一下测试用例函数:
function test_Increment() public { counter.increment(); // 调用被测试合约的函数 increment assertEq(counter.number(), 100); // 判断被测试合约中 number 的值是否等于 100 }
最后的断言语句,修改成判断运行结果是否等于100。 再次运行测试命令:
forge test
我们可以看到输出结果:
[⠰] Compiling...
[⠔] Compiling 1 files with 0.8.13
[⠒] Solc 0.8.13 finished in 2.04s
Compiler run successful!
Ran 2 tests for test/Counter.t.sol:CounterTest
[FAIL. Reason: assertion failed: 1 != 100] test_Increment() (gas: 31322)
[PASS] test_SetNumber(uint256) (runs: 256, μ: 30643, ~: 31265)
Suite result: FAILED. 1 passed; 1 failed; 0 skipped; finished in 24.72ms (15.96ms CPU time)
Ran 1 test suite in 321.10ms (24.72ms CPU time): 1 tests passed, 1 failed, 0 skipped (2 total tests)
Failing tests:
Encountered 1 failing test in test/Counter.t.sol:CounterTest
[FAIL. Reason: assertion failed: 1 != 100] test_Increment() (gas: 31322)
Encountered a total of 1 failing tests, 1 tests succeeded
表示测试未通过,抛出了红色标记的异常。