跳转到主要内容

如何在测试中模拟 Solidity 智能合约

Solidity
智能合约
测试
模拟
中级
马库斯·瓦斯
2020年5月2日
6 分钟阅读

模拟对象(Mock objects) (opens in a new tab)是面向对象编程中常见的设计模式。它源于古法语单词“mocquer”,意为“取笑”,后来演变为“模仿真实事物”,这实际上就是我们在编程中所做的事情。请只在你想取笑你的智能合约时才去取笑它们,但只要可能,就尽量模拟(mock)它们。这会让你的生活更轻松。

使用模拟进行合约单元测试

模拟合约本质上意味着创建该合约的第二个版本,其行为与原始版本非常相似,但开发者可以轻松控制它。你经常会遇到复杂的合约,而你只想对合约的一小部分进行单元测试。问题是,如果测试这一小部分需要一个很难达到的特定合约状态,该怎么办?

你可以每次都编写复杂的测试设置逻辑,使合约进入所需的状态,或者你可以编写一个模拟(mock)。通过继承来模拟合约非常简单。只需创建一个继承自原始合约的第二个模拟合约即可。现在你可以在模拟合约中重写(override)函数。让我们通过一个例子来看看。

示例:私有 ERC-20

我们使用一个带有初始私有时间的 ERC-20 合约示例。所有者可以管理私有用户,一开始只有这些用户才被允许接收代币。一旦经过特定时间,所有人都将被允许使用代币。如果你好奇的话,我们使用的是新版欧本齐柏林(OpenZeppelin)合约 v3 中的 _beforeTokenTransfer (opens in a new tab) 钩子。

现在让我们来模拟它。

你将收到以下错误消息之一:

  • PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.
  • PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.

由于我们使用的是新的 0.6 Solidity 版本,我们必须为可以被重写的函数添加 virtual 关键字,并为重写函数添加 override 关键字。因此,让我们将它们添加到这两个 isPublic 函数中。

现在在你的单元测试中,你可以改用 PrivateERC20Mock。当你想测试私有使用时间内的行为时,使用 setIsPublic(false),同样地,使用 setIsPublic(true) 来测试公共使用时间。当然,在我们的示例中,我们也可以直接使用时间辅助工具 (opens in a new tab)来相应地更改时间。但是现在模拟的概念应该很清楚了,你可以想象在某些场景下,事情并不像简单地推进时间那么容易。

模拟多个合约

如果你必须为每一个模拟创建一个单独的合约,事情可能会变得一团糟。如果这让你感到困扰,你可以看看 MockContract (opens in a new tab) 库。它允许你动态地重写和更改合约的行为。然而,它仅适用于模拟对另一个合约的调用,因此它不适用于我们的示例。

模拟可以更加强大

模拟的强大之处不止于此。

  • 添加函数:不仅重写特定函数很有用,仅仅添加额外的函数也很有用。对于代币来说,一个很好的例子就是添加一个额外的 mint 函数,允许任何用户免费获取新代币。
  • 在测试网中的使用:当你在测试网上将合约与你的去中心化应用 (dapp) 一起部署和测试时,考虑使用模拟版本。除非万不得已,否则避免重写函数。毕竟你想测试的是真实的逻辑。但是,例如添加一个重置函数可能会很有用,它只需将合约状态重置为初始状态,而无需重新部署。显然,你不会希望在主网(Mainnet)合约中包含该功能。