测试合约
本章学习如何使用 Hardhat
测试智能合约。
编写自动化测试脚本是合约测试中最重要的工作之一。
我们在合约测试脚本中,将使用 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
测试框架中,describe
和 it
是两个主要的关键字,用于组织和编写测试用例。
它们各自有不同的用途和分工:
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
对重复测试设置进行了优化,将会大大提高测试速度。