使用测试驱动开发模式编写智能合约

时间:2024-03-11 19:05:02

hardhat简介

  • hardhat是一个以太坊智能合约开发框架,主要用于简化和加速以太坊区块链上的智能合约开、测试和部署,提供了许多工具帮助开发人员更轻松地构建和维护智能合约项目,以下是他的一些主要功能:
    • 智能合约开发:hardhat提供了强大的开发环境,支持solidity语言,允许开发人员轻松编写、调试和测试智能合约。
    • 智能合约测试:hardhat提供了一个集成的测试框架,使开发人员能编写和运行针对智能合约的自动化测试,这有助于确保合约在不同场景下的正确性和安全性。
    • 本地开发网络:hardhat支持在本地运行一个以太坊节点,以便在开发过程中快速测试和调试智能合约,使得开发人员无需连接到主网络,提高了开发效率。
    • 集成测试和部署:hardhat提供了工具和脚本,方便集成测试和部署智能合约到以太坊主网或测试网,有助于合约在真实环境中正常运行。
    • 脚本和任务:hardhat允许开发人员编写自定义脚本和任务,以执行特定的操作,例如部署合约、验证代码等,为自动化开发流程提供了更大的灵活性。
    • 插件系统:hardhat的插件系统使得开发人员能扩展框架的功能,以满足项目的特定需求,可以使用现有的插件,也可以编写自己的定制插件。
    • 与其它开发工具的集成: Hardhat 与其他常用的开发工具集成,例如 Truffle、ethers.js 等,为开发人员提供更多选择和灵活性。

安装hardhat

  • 前提是要安装好node.js。
  • 在项目目录里打开终端使用mkdir 文件名命令来创建一个新的文件夹
  • 然后使用cd 文件名命令转到该目录
  • 使用npm install --save-dev hardhat命令安装hardhat
  • 输入npx hardhat init命令进行初始化
  • 该某目录下就会多出这些文件

使用测试驱动开发模式编写智能合约

  • 基于该框架,使用vscode打开该文件夹,在test目录下新建test.js文件,在contracts目录下新建Mailbox.sol合约文件
  • 示例代码
    注意:要进入我们之前安装hardhat的根目录,否则他会显示错误信息Error HH1: You are not inside a Hardhat project.,表示你当前不在一个hardhat项目中,我这里创建的目录是my-new-project,因此要在该目录下创建文件以及运行
    js代码

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Mailbox", async () => {
  it("should get mailbox contract", async () => {
    const mailboxContract = await ethers.getContractFactory("Mailbox");
  });

  it("should get total letters in the box", async () => {
    const mailboxContract = await ethers.getContractFactory("Mailbox");
    const mailbox = await mailboxContract.deploy();
    expect(await mailbox.totalLetters()).to.equal(0);
  });

  it("should increase by one when got new letter", async () => {
    const mailboxContract = await ethers.getContractFactory("Mailbox");
    const mailbox = await mailboxContract.deploy();

    await mailbox.write("Hello");
    expect(await mailbox.totalLetters()).to.equal(1);

  })

  it("should get mail content", async () => {
    const mailboxContract = await ethers.getContractFactory("Mailbox");
    const mailbox = await mailboxContract.deploy();

    await mailbox.write("Hello");
    const letters = await mailbox.read();
    expect(letters[0].letter).to.equal("Hello");

  })

  it("should get mail sender", async () => {
    const mailboxContract = await ethers.getContractFactory("Mailbox");
    const mailbox = await mailboxContract.deploy();

    await mailbox.write("Hello");
    const letters = await mailbox.read();
    //这里记得测试后将0x替换成终端给出的地址
    expect(letters[0].sender).to.equal("0x");
  })
});

合约代码

pragma solidity 0.8.24;
contract Mailbox {
//这里没有赋初值的话,初始值为0
  uint public totalLetters;

  struct Letter {
    string letter;
    address sender;
  }

  Letter[] private letters;

  function write(string memory letter) public {
    totalLetters++;
    letters.push(Letter(letter, msg.sender));
  }

  function read() public view returns (Letter[] memory) {
    return letters;
  }
}

终端界面输入npx hardhat test命令,当显示全绿表示测试通过
在这里插入图片描述

学习笔记
  • const { expect } = require("chai");
    这行代码使用了JavaScript的解构赋值语法,表示声明了一个expect常量,并从chai这个模块中 找到名为expect的属性并赋值给该expect常量。
    如果不使用解构赋值就使用const expect = require("chai")命令,该命令导入了chai的整个模块,使用解构赋值语法的好处就是只导入模块中真正需要的部分,减少不必要的代码依赖。当模块中有多个导出时可以防止导入的变量名和其它变量名发生冲突。
    这里的require用于加载指定模块
  • describe("Mailbox", async () => {})
    该代码是使用一种测试框架编写的测试用例描述。这种测试框架用于编写和组织测试用例,确保代码的正确性和可靠性。
    describe函数接收两个参数,一个描述字符串,也就是我们这里的"Mailbox",一个回调函数,即async () => {}。该回调函数是一个 异步函数(使用 async 关键字标记)。
    在describe函数的回调函数中 ,你可以添加一个或多个测试用例。测试用例使用测试框架提供的其它函数(如it)编写的。
  • it("should get mail sender", async () => {})
    是一个测试用例,接收两个参数,一个字符串,一个异步函数
  • const mailboxContract = await ethers.getContractFactory("Mailbox");
    使用ethers.getContractFactory获取一个名为Mailbox的智能合约,并将其实例化为一个常量对象。
  • const mailbox = await mailboxContract.deploy();
    mailboxContract.deploy()是一个异步操作,它返回一个promise对象,该对象在解析时包含一个已部署的"Mailbox"智能合约实例,await关键字用于等待Promise对象的解析,在这里用英语等待智能合约的部署完成。最后,将解析后的已部署智能合约实例赋值给常量对象mailbox。这样,我们就可以使用mailbox来调用"Mailbox"智能合约的方法和属性。
  • await mailbox.write("Hello");
    调用合约的write函数向信箱写入一条消息“Hello”
  • const letters = await mailbox.read();
    调用合约的read函数读取信箱中的邮箱
  • expect(letters[0].sender).to.equal('0x');
    使用expect函数进行断言,letters[0].sender从先前测试中获取到信箱中第一封信件的发送者地址,在终端测试后会给出该地址,替换掉0x即可测试通过。to.equal是一个匹配器。