10W 以太坊 ethereum hardhat : 测试合约

介绍
编写测试
使用不同的帐户
使用fixture重用常见的测试设置
完整代码
hardhat Tutorials , hardhat 教程
Contact 联系方式

• 介绍

在构建智能合约时编写自动化测试至关重要,因为您的用户的钱是危险的。

为了测试我们的合约,我们将使用 Hardhat Network,这是一个专为开发而设计的本地以太坊网络。它内置在 Hardhat 中,并用作默认网络。您无需设置任何内容即可使用它。

在我们的测试中,我们将使用 ethers.js 与我们在上一节中构建的以太坊合约进行交互,我们将使用 Mocha 作为我们的测试运行器。

• 编写测试

在我们的项目根目录中创建一个名为 test 的新目录,并在其中创建一个名为 Token.js 的新文件。

让我们从下面的代码开始。我们接下来会解释它,但现在将它粘贴到 Token.js 中:

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

describe("Token contract", function () {
    
    
  it("Deployment should assign the total supply of tokens to the owner", async function () {
    
    
    const [owner] = await ethers.getSigners();

    const Token = await ethers.getContractFactory("Token");

    const hardhatToken = await Token.deploy();

    const ownerBalance = await hardhatToken.balanceOf(owner.address);
    expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
  });
});

在您的终端中运行“npx hardhat test”。您应该看到以下输出:

$ npx hardhat test

  Token contract
    ✓ Deployment should assign the total supply of tokens to the owner (654ms)


  1 passing (663ms)

这意味着测试通过了。现在让我们解释每一行:

const [owner] = await ethers.getSigners();

ethers.js 中的“Signer”是一个代表以太坊账户的对象。它用于向合约和其他账户发送交易。在这里,我们获得了我们连接的节点中的帐户列表,在本例中是 Hardhat Network,我们只保留第一个。

ethers 变量在全局范围内可用。如果你喜欢你的代码总是明确的,你可以在顶部添加这一行:

const {
    
     ethers } = require("hardhat");
const Token = await ethers.getContractFactory("Token");

ethers.js 中的 ContractFactory 是一个用于部署新智能合约的抽象,所以这里的 Token 是我们代币合约实例的工厂。

const hardhatToken = await Token.deploy();

ContractFactory 上调用 deploy() 将启动部署,并返回解析为 ContractPromise。这是为您的每个智能合约功能提供方法的对象。

const ownerBalance = await hardhatToken.balanceOf(owner.address);

部署合约后,我们可以在 hardhatToken 上调用我们的合约方法。这里我们通过调用合约的 balanceOf() 方法获取所有者账户的余额。

回想一下,部署令牌的帐户获得了全部供应。默认情况下,ContractFactoryContract 实例连接到第一个签名者。这意味着 owner 变量中的帐户执行了部署,而 balanceOf() 应该返回整个供应量。

expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);

在这里,我们再次使用我们的 Contract 实例在我们的 Solidity 代码中调用智能合约函数。 totalSupply() 返回代币的供应量,我们正在检查它是否等于 ownerBalance,因为它应该是。

为此,我们使用 Chai,它是一个流行的 JavaScript 断言库。这些断言函数称为“匹配器”,我们在这里使用的函数来自 @nomicfoundation/hardhat-chai-matchers插件,它用许多对测试智能合约有用的匹配器扩展了 Chai。

• 使用不同的帐户

如果您需要通过从默认帐户以外的帐户(或 ethers.js 术语中的“签名者”)发送交易来测试您的代码,您可以在 ethers.js 的“合同”对象上使用“connect()”方法将其连接到不同的帐户,如下所示:

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

describe("Token contract", function () {
  // ...previous test...

  it("Should transfer tokens between accounts", async function() {
    const [owner, addr1, addr2] = await ethers.getSigners();

    const Token = await ethers.getContractFactory("Token");

    const hardhatToken = await Token.deploy();

    // Transfer 50 tokens from owner to addr1
    await hardhatToken.transfer(addr1.address, 50);
    expect(await hardhatToken.balanceOf(addr1.address)).to.equal(50);

    // Transfer 50 tokens from addr1 to addr2
    await hardhatToken.connect(addr1).transfer(addr2.address, 50);
    expect(await hardhatToken.balanceOf(addr2.address)).to.equal(50);
  });
});

• 使用fixture重用常见的测试设置

我们编写的两个测试从它们的设置开始,在这种情况下,这意味着部署代币合约。在更复杂的项目中,此设置可能涉及多个部署和其他事务。在每次测试中都这样做意味着大量的代码重复。另外,在每个测试开始时执行许多事务会使测试套件变得更慢。

您可以使用 fixtures 避免代码重复并提高测试套件的性能。固定装置是一个设置函数,仅在第一次调用时运行。在随后的调用中,Hardhat 不会重新运行它,而是将网络的状态重置为夹具最初执行后的状态。

const {
    
     loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
const {
    
     expect } = require("chai");

describe("Token contract", function () {
    
    
  async function deployTokenFixture() {
    
    
    const Token = await ethers.getContractFactory("Token");
    const [owner, addr1, addr2] = await ethers.getSigners();

    const hardhatToken = await Token.deploy();

    await hardhatToken.deployed();

    // Fixtures can return anything you consider useful for your tests
    return {
    
     Token, hardhatToken, owner, addr1, addr2 };
  }

  it("Should assign the total supply of tokens to the owner", async function () {
    
    
    const {
    
     hardhatToken, owner } = await loadFixture(deployTokenFixture);

    const ownerBalance = await hardhatToken.balanceOf(owner.address);
    expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
  });

  it("Should transfer tokens between accounts", async function () {
    
    
    const {
    
     hardhatToken, owner, addr1, addr2 } = await loadFixture(
      deployTokenFixture
    );

    // Transfer 50 tokens from owner to addr1
    await expect(
      hardhatToken.transfer(addr1.address, 50)
    ).to.changeTokenBalances(hardhatToken, [owner, addr1], [-50, 50]);

    // Transfer 50 tokens from addr1 to addr2
    // We use .connect(signer) to send a transaction from another account
    await expect(
      hardhatToken.connect(addr1).transfer(addr2.address, 50)
    ).to.changeTokenBalances(hardhatToken, [addr1, addr2], [-50, 50]);
  });
});

在这里,我们编写了一个 deployTokenFixture 函数,它进行必要的设置并返回我们稍后在测试中使用的每个值。然后在每个测试中,我们使用 loadFixture 来运行夹具并获取这些值。 loadFixture 将首次运行设置,并在其他测试中快速返回该状态。

• 完整代码

现在我们已经介绍了测试合约所需的基础知识,这里有一个完整的代币测试套件,其中包含有关 Mocha 以及如何构建测试的大量附加信息。我们建议您仔细阅读。

// This is an example test file. Hardhat will run every *.js file in `test/`,
// so feel free to add new ones.

// Hardhat tests are normally written with Mocha and Chai.

// We import Chai to use its asserting functions here.
const {
    
     expect } = require("chai");

// We use `loadFixture` to share common setups (or fixtures) between tests.
// Using this simplifies your tests and makes them run faster, by taking
// advantage of Hardhat Network's snapshot functionality.
const {
    
     loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

// `describe` is a Mocha function that allows you to organize your tests.
// Having your tests organized makes debugging them easier. All Mocha
// functions are available in the global scope.
//
// `describe` receives the name of a section of your test suite, and a
// callback. The callback must define the tests of that section. This callback
// can't be an async function.
describe("Token contract", function () {
    
    
  // We define a fixture to reuse the same setup in every test. We use
  // loadFixture to run this setup once, snapshot that state, and reset Hardhat
  // Network to that snapshopt in every test.
  async function deployTokenFixture() {
    
    
    // Get the ContractFactory and Signers here.
    const Token = await ethers.getContractFactory("Token");
    const [owner, addr1, addr2] = await ethers.getSigners();

    // To deploy our contract, we just have to call Token.deploy() and await
    // its deployed() method, which happens onces its transaction has been
    // mined.
    const hardhatToken = await Token.deploy();

    await hardhatToken.deployed();

    // Fixtures can return anything you consider useful for your tests
    return {
    
     Token, hardhatToken, owner, addr1, addr2 };
  }

  // You can nest describe calls to create subsections.
  describe("Deployment", function () {
    
    
    // `it` is another Mocha function. This is the one you use to define each
    // of your tests. It receives the test name, and a callback function.
    //
    // If the callback function is async, Mocha will `await` it.
    it("Should set the right owner", async function () {
    
    
      // We use loadFixture to setup our environment, and then assert that
      // things went well
      const {
    
     hardhatToken, owner } = await loadFixture(deployTokenFixture);

      // `expect` receives a value and wraps it in an assertion object. These
      // objects have a lot of utility methods to assert values.

      // This test expects the owner variable stored in the contract to be
      // equal to our Signer's owner.
      expect(await hardhatToken.owner()).to.equal(owner.address);
    });

    it("Should assign the total supply of tokens to the owner", async function () {
    
    
      const {
    
     hardhatToken, owner } = await loadFixture(deployTokenFixture);
      const ownerBalance = await hardhatToken.balanceOf(owner.address);
      expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
    });
  });

  describe("Transactions", function () {
    
    
    it("Should transfer tokens between accounts", async function () {
    
    
      const {
    
     hardhatToken, owner, addr1, addr2 } = await loadFixture(
        deployTokenFixture
      );
      // Transfer 50 tokens from owner to addr1
      await expect(
        hardhatToken.transfer(addr1.address, 50)
      ).to.changeTokenBalances(hardhatToken, [owner, addr1], [-50, 50]);

      // Transfer 50 tokens from addr1 to addr2
      // We use .connect(signer) to send a transaction from another account
      await expect(
        hardhatToken.connect(addr1).transfer(addr2.address, 50)
      ).to.changeTokenBalances(hardhatToken, [addr1, addr2], [-50, 50]);
    });

    it("should emit Transfer events", async function () {
    
    
      const {
    
     hardhatToken, owner, addr1, addr2 } = await loadFixture(
        deployTokenFixture
      );

      // Transfer 50 tokens from owner to addr1
      await expect(hardhatToken.transfer(addr1.address, 50))
        .to.emit(hardhatToken, "Transfer")
        .withArgs(owner.address, addr1.address, 50);

      // Transfer 50 tokens from addr1 to addr2
      // We use .connect(signer) to send a transaction from another account
      await expect(hardhatToken.connect(addr1).transfer(addr2.address, 50))
        .to.emit(hardhatToken, "Transfer")
        .withArgs(addr1.address, addr2.address, 50);
    });

    it("Should fail if sender doesn't have enough tokens", async function () {
    
    
      const {
    
     hardhatToken, owner, addr1 } = await loadFixture(
        deployTokenFixture
      );
      const initialOwnerBalance = await hardhatToken.balanceOf(owner.address);

      // Try to send 1 token from addr1 (0 tokens) to owner (1000 tokens).
      // `require` will evaluate false and revert the transaction.
      await expect(
        hardhatToken.connect(addr1).transfer(owner.address, 1)
      ).to.be.revertedWith("Not enough tokens");

      // Owner balance shouldn't have changed.
      expect(await hardhatToken.balanceOf(owner.address)).to.equal(
        initialOwnerBalance
      );
    });
  });
});

这是 npx hardhat test 的输出在完整测试套件中的样子:

$ npx hardhat test

  Token contract
    Deployment
      ✓ Should set the right owner
      ✓ Should assign the total supply of tokens to the owner
    Transactions
      ✓ Should transfer tokens between accounts (199ms)
      ✓ Should fail if sender doesn’t have enough tokens
      ✓ Should update balances after transfers (111ms)


  5 passing (1s)

请记住,当您运行“npx hardhat test”时,如果您的合约自上次运行测试以来发生了变化,则会自动编译它们。

• hardhat Tutorials , hardhat 教程

CN 中文 Github hardhat 教程 : github.com/565ee/hardhat_CN
CN 中文 CSDN hardhat 教程 : blog.csdn.net/wx468116118
EN 英文 Github hardhat Tutorials : github.com/565ee/hardhat_EN

• Contact 联系方式

Homepage : 565.ee
微信公众号 : wx468116118
微信 QQ : 468116118
GitHub : github.com/565ee
CSDN : blog.csdn.net/wx468116118
Email : [email protected]

猜你喜欢

转载自blog.csdn.net/wx468116118/article/details/125863764