本文介绍了如何通过编写自动化测试来验证智能合约的行为。内容包括搭建测试环境(使用本地区块链)、编写单元测试(使用Chai断言库),以及执行复杂断言的方式(使用OpenZeppelin Test Helpers)。文章还提及了持续集成服务(如CircleCI)的设置,以便每次提交代码到GitHub时自动运行测试。
在区块链环境中,一个简单的错误可能会让你损失所有的资金 - 更糟糕的是,你用户的资金!本指南将帮助你编写自动化测试,验证你的应用程序是否按照你预期的那样运行,从而开发出健壮的应用程序。
我们将涵盖以下主题:
测试技术范围广泛,从简单的手动验证到复杂的端到端设置,所有这些都有其自身的用途。
然而,说到智能合约开发,实践表明合约单元测试(unit testing)特别有价值。这些测试编写简单,运行速度快,可以让你自信地在代码中添加功能和修复错误。
智能合约单元测试由多个小的、集中的测试组成,每个测试都检查合约的一小部分是否正确。它们通常可以用构成规范的单个句子来表达,例如“管理员能够暂停合约”、“转移 token 会发出事件”或“非管理员不能铸造新的 token”。
你可能想知道我们如何运行这些测试,因为智能合约是在区块链内部执行的。使用实际的以太坊网络会非常昂贵,虽然测试网是免费的,但它们也很慢(区块时间为 12 秒或更长)。如果我们打算在每次更改代码时运行数百个测试,我们需要更好的东西。
我们将使用一种称为本地区块链的东西:这是真实区块链的精简版本,与互联网断开连接,在你自己的机器上运行。这将大大简化事情:你不需要获得以太币,并且会立即挖掘出新的区块。
我们将使用 Chai 断言来进行单元测试,可以通过安装 Hardhat Toolbox 来使用。
$ npm install --save-dev @nomicfoundation/hardhat-toolbox
我们会将测试文件保存在 test
目录中。测试的最佳结构是镜像 contracts
目录:对于那里的每个 .sol
文件,创建一个对应的测试文件。
是时候编写我们的第一个测试了!这些测试将测试之前指南中的 Box
合约的属性:一个简单的合约,允许你 retrieve
所有者之前store
的值。
在你的项目根目录中创建一个 test
目录。我们将测试保存为 test/Box.test.js
。每个测试 .js
文件通常包含单个合约的测试,并以该合约命名。
// test/Box.test.js
// 加载依赖
const { expect } = require('chai');
// 启动测试块
describe('Box', function () {
before(async function () {
this.Box = await ethers.getContractFactory('Box');
});
beforeEach(async function () {
this.box = await this.Box.deploy();
await this.box.waitForDeployment();
});
// 测试用例
it('retrieve 返回先前存储的值', async function () {
// 存储一个值
await this.box.store(42);
// 测试返回值是否相同
// 注意,我们需要使用字符串来比较 256 位整数
expect((await this.box.retrieve()).toString()).to.equal('42');
});
});
已经有很多关于如何构建单元测试的书籍。查看 Moloch 测试指南,了解一套为测试 Solidity 智能合约而设计的原则。 |
我们现在准备运行我们的测试了!
运行 npx hardhat test
将执行 test
目录中的所有测试,检查你的合约是否按照你预期的方式工作:
$ npx hardhat test
Box
✓ retrieve returns a value previously stored
1 passing (578ms)
在这一点上,设置一个持续集成服务(例如 CircleCI)也是一个非常好的主意,以便在每次将代码提交到 GitHub 时自动运行测试。
你的合约的许多有趣的属性可能难以捕获,例如:
验证合约是否在错误时恢复
衡量一个帐户的以太币余额变化了多少
检查是否发出了正确的事件
OpenZeppelin Test Helpers 是一个旨在帮助你测试所有这些属性的库。它还将简化模拟区块链上时间流逝和处理非常大的数字的任务。
OpenZeppelin Test Helpers 基于 web3.js,因此 Hardhat 用户应该使用 Truffle 插件以实现兼容性。但是,我们建议使用Hardhat Chai Matchers 作为 Ethers.js 更好的支持替代方案。 |
要安装 OpenZeppelin Test Helpers,请运行:
$ npm install --save-dev @openzeppelin/test-helpers
然后,我们可以更新我们的测试,使用 OpenZeppelin Test Helpers 来支持非常大的数字,检查是否发出了事件,并检查交易是否恢复。
// test/Box.test.js
// 加载依赖项
const { expect } = require('chai');
// 从 Test Helpers 导入实用程序
const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
// 加载编译后的工件
const Box = artifacts.require('Box');
// 启动测试块
contract('Box', function ([ owner, other ]) {
// 使用大整数(“大数字”)
const value = new BN('42');
beforeEach(async function () {
this.box = await Box.new({ from: owner });
});
it('retrieve 返回先前存储的值', async function () {
await this.box.store(value, { from: owner });
// 使用大整数比较
expect(await this.box.retrieve()).to.be.bignumber.equal(value);
});
it('store 发出事件', async function () {
const receipt = await this.box.store(value, { from: owner });
// 测试是否发出了带有新值的 ValueChanged 事件
expectEvent(receipt, 'ValueChanged', { value: value });
});
it('非所有者无法存储值', async function () {
// 测试交易是否恢复
await expectRevert(
this.box.store(value, { from: other }),
'Ownable: caller is not the owner',
);
});
});
这些测试将测试之前指南中的 Ownable
Box
合约的属性:一个简单的合约,允许你 retrieve
所有者之前store
的值。
再次运行你的测试,以查看 Test Helpers 的实际效果:
$ npx hardhat test
...
Contract: Box
✓ retrieve 返回先前存储的值
✓ store 发出事件
✓ non owner cannot store a value (588ms)
3 passing (753ms)
Test Helpers 将让你编写强大的断言,而无需担心底层以太坊库的低级细节。要了解有关你可以用它们做什么的更多信息,请访问它们的 API 参考。
一旦你彻底测试了你的合约并确信它们是正确的,你将需要将它们部署到真实的网络并开始与它们交互。以下指南将让你快速了解这些主题:
- 原文链接: docs.openzeppelin.com/le...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!