编写自动化智能合约测试 - OpenZeppelin 文档

本文介绍了如何通过编写自动化测试来验证智能合约的行为。内容包括搭建测试环境(使用本地区块链)、编写单元测试(使用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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。