• 介绍
• 编写测试
• 使用不同的帐户
• 使用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()
将启动部署,并返回解析为 Contract
的 Promise
。这是为您的每个智能合约功能提供方法的对象。
部署合约后,我们可以在 hardhatToken
上调用我们的合约方法。这里我们通过调用合约的 balanceOf()
方法获取所有者账户的余额。
回想一下,部署令牌的帐户获得了全部供应。默认情况下,ContractFactory
和 Contract
实例连接到第一个签名者。这意味着 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]