淘先锋技术网

首页 1 2 3 4 5 6 7

介绍
编写测试
使用不同的帐户
使用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)

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

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

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

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

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

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

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

在这里,我们再次使用我们的 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]