测试合约

本章学习如何使用 Hardhat 测试智能合约。

推特@Hita_DAO    DiscordHitaDAO

编写自动化测试脚本是合约测试中最重要的工作之一。

我们在合约测试脚本中,将使用 ethers.js 库与合约进行交互,并使用 JavaScript 测试框架 Mocha 进行测试。

hardhat 中内置了 Hardhat Network,这是专为智能合约开发而设计的本地以太坊网络。

它允许开发人员在本地计算机上部署合约、运行测试和代码调试,无需连接到真实的以太坊网络。 

1.  编写测试用例

我们需要在 hardhat 项目的根目录中创建一个名为 test 的固定目录,然后创建一个名为 Calculator.js 的测试脚本文件。

 

Calculator.js 文件的代码如下:

// 导入 Chai 断言库的 expect 函数
const { expect } = require("chai");

// 定义一个测试套件,用于测试 Calculator 合约
describe("Calculator contract", async function () {
  
  // 部署合约,并返回合约实例
  async function deployCalculator() {
    // 获取 Calculator 合约的合约工厂
    const Calculator = await ethers.getContractFactory("Calculator");
    // 部署 Calculator 合约,获得合约实例 calculator
    const calculator = await Calculator.deploy();
    // 返回合约实例
    return {calculator};
  };
 
  // 第一个测试用例:测试 add 函数是否正确相加两个数字
  it("should add two numbers correctly", async function () {
    // 部署合约,获得合约实例
    const {calculator} = await deployCalculator();
    // 调用 calculator 合约的 add 函数,传入参数 5 和 3
    const result = await calculator.add(5, 3);
    // 使用 Chai 断言库的 expect 函数检查结果是否等于 8
    expect(result).to.equal(8);
  });

  // 第二个测试用例:测试 sub 函数是否正确相减两个数字
  it("should subtract two numbers correctly", async function () {
    // 部署合约,获得合约实例
    const {calculator} = await deployCalculator();
    // 调用 calculator 合约的 sub 函数,传入参数 10 和 4
    const result = await calculator.sub(10, 4);
    // 使用 Chai 断言库的 expect 函数检查结果是否等于 6
    expect(result).to.equal(6);
  });
});

Mocha 测试框架中,describeit 是两个主要的关键字,用于组织和编写测试用例。

它们各自有不同的用途和分工:

1.1 describe

describe 用于创建测试套件,测试套件是指一组相关联的测试用例。简单来说,就是对测试用例进行分组。

一个测试套件中可以包含一个或多个测试用例,也就是 it 模块,例如:

describe("Calculator contract", function () {
  it("...", function () {
  });

  it("...", function () {
  });
});

其中:Calculator contract 就是测试套件的描述。

function() {} 包含2个测试用例,也就是2个 it 块。

expect 语句就是期望的输出结果。

describe 中可以嵌套其它 describe,用来创建更深层次的测试组织结构,以便更好地组织测试用例。

1.2 it

it 用于编写具体的测试用例。每个 it块测试一个特定的方面或功能,并提供对期望行为的描述,例如:

it("should add two numbers correctly", function() {
   ....
});

其中:should add two numbers correctly 就是对期望行为的描述。

function() {} 是具体的测试代码,通常包含一个或多个断言,用于验证代码的行为。

it 块应该在 describe 内部使用,从而将测试用例与对应的测试套件关联起来。

2. 运行自动化测试脚本

vscode 终端或者在命令行中,执行测试命令:

npx hardhat test

输出测试:

  Calculator contract
    ✔ should add two numbers correctly (2129ms)
    ✔ should subtract two numbers correctly
  2 passing (2s)

表示测试组 Calculator contract 中的两个测试用例,测试通过。

3. 使用 Fixture 优化测试设置

在复杂项目中,测试用例通常需要共享一些相同的初始化设置,比如部署智能合约或创建特定的测试环境。

如果在每个测试用例中都重复这些设置,将会导致代码冗余,并且可能降低测试套件的性能。

为了解决这个问题,我们可以使用 Fixture(固定程序)。

Fixture 是一个函数,它在第一次调用时运行,并将其结果缓存起来,以便在后续测试用例中共享。

hardhat 提供了 loadFixture 函数,它可以轻松实现这个目标。

下面示例,展示了如何使用 Fixture 来组织和优化测试代码:

// 导入 loadFixture 函数
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

// 导入 Chai 断言库的 expect 函数
const { expect } = require("chai");

// 定义一个测试套件,用于测试 Calculator 合约
describe("Calculator contract", async function () {
  
  // 部署合约,并返回合约实例
  async function deployCalculator() {
    // 获取 Calculator 合约的合约工厂
    const Calculator = await ethers.getContractFactory("Calculator");
    // 部署 Calculator 合约,获得合约实例 calculator
    const calculator = await Calculator.deploy();
    // 返回合约实例
    return {calculator};
  };
 
  // 第一个测试用例:测试 add 函数是否正确相加两个数字
  it("should add two numbers correctly", async function () {
    // 部署合约,获得合约实例
    const {calculator} = await loadFixture(deployCalculator);
    // 调用 calculator 合约的 add 函数,传入参数 5 和 3
    const result = await calculator.add(5, 3);
    // 使用 Chai 断言库的 expect 函数检查结果是否等于 8
    expect(result).to.equal(8);
  });

  // 第二个测试用例:测试 sub 函数是否正确相减两个数字
  it("should subtract two numbers correctly", async function () {
    // 部署合约,获得合约实例
    const {calculator} = await loadFixture(deployCalculator);
    // 调用 calculator 合约的 sub 函数,传入参数 10 和 4
    const result = await calculator.sub(10, 4);
    // 使用 Chai 断言库的 expect 函数检查结果是否等于 6
    expect(result).to.equal(6);
  });
});

通过使用 loadFixture 对重复测试设置进行了优化,将会大大提高测试速度。