要测试 Solidity 的 internal 函数,请创建一个继承自被测合约的子合约,将父合约的 internal 函数包装在一个 external 函数中,然后在子合约中测试这个 external 函数。
Foundry 将这种继承合约称为 “harness”,而其他人则称之为 “fixture”。
不要为了方便扩展而将函数更改为 virtual 或 public,你需要测试的是你实际将要部署的合约。
这里有一个例子。
contract InternalFunction {
function calculateReward(uint256 depositTime) **internal** view returns (uint256 reward) {
reward = (block.timestamp - depositTime) \* REWARD\_RATE\_PER\_SECOND;
}
}
上面的函数为流逝的每个时间单位提供了线性的奖励率。
fixture(或 harness)将如下所示:
contract InternalFunctionHarness is InternalFunction {
function calculateReward(uint256 depositTime) **external** view returns (uint256 reward) {
reward = super.calculateReward(depositTime);
}
}
当你调用与子合约同名的父合约函数时,必须使用 super 关键字,否则该函数将调用自身并陷入无限递归。
或者,你可以显式地将测试函数标记为 harness 或 fixture,如下所示:
contract InternalFunctionHarness is InternalFunction {
function calculateReward\_HARNESS(uint256 depositTime) **external** view returns (uint256 reward) {
reward = calculateReward(depositTime);
}
}
不要将函数更改为 public
将函数更改为 public 并不是一个好的解决方案,因为这会增加合约的大小。如果一个函数不需要是 public 的,那就不要把它变成 public。这会增加部署阶段以及其他函数执行时的 Gas 成本。
当合约收到一笔交易时,它必须通过线性或二分搜索将函数选择器与所有 public 函数的选择器进行比较。无论哪种情况,这都会增加需要搜索的选择器数量。此外,添加的选择器意味着增加了字节码,从而提高了部署成本。
不要重写 virtual 函数
假设我们有以下合约:
contract InternalFunction {
function calculateReward(uint256 depositTime) **internal** view virtual returns (uint256 reward) {
reward = (block.timestamp - depositTime) \* REWARD\_RATE\_PER\_SECOND;
}
}
为了方便起见,直接在 fixture 上重写它可能很有诱惑力,但这并不可取,因为你最终会重复代码。而且如果你的代码实现与父合约在 harness 中产生了分歧,你就将不再是真正地测试你的业务逻辑。
请注意,这种方法迫使我们复制并粘贴原始代码:
contract InternalFunctionHarness is InternalFunction {
function calculateReward(uint256 depositTime) **external** view override returns (uint256 reward) {
reward = (block.timestamp - depositTime) \* REWARD\_RATE\_PER\_SECOND;
}
}
测试 private Solidity 函数怎么办?
在 Solidity 中无法测试 private 函数,因为它们对子合约不可见。在合约编译之后,internal 函数和 private 函数之间的区别就不存在了。因此,你可以将 private 函数更改为 internal,这不会对 Gas 成本产生任何负面影响。
作为留给读者的练习,你可以对以下代码进行基准测试,看看将 foo 更改为 internal 是否会影响 Gas 成本。
contract A {
// change this to be private
function foo() **internal** pure returns (uint256 f) {
f = 2;
}
function bar() **internal** pure returns (uint256 b) {
b = foo();
}
}
contract B is A {
// 146 gas: 0.8.7 no optimizer
function baz() **external** pure returns (uint256 b) {
b = bar();
}
}
了解更多
查看我们的高级 Solidity Bootcamp 以学习更多高级测试方法。
我们还提供免费的 Solidity 教程 帮助你入门。
最初发布于 2023 年 4 月 6 日