— введение
— Написание тестов
— Использование другого аккаунта
— Повторное использование общих настроек тестов с помощью фикстур
— Полный охват
— Учебники по hardhat , hardhat 教程
— Контакт 联系方式
— введение
Написание автоматизированных тестов при создании смарт-контрактов имеет огромное значение, поскольку на кону стоят деньги пользователей.
Для тестирования нашего контракта мы будем использовать Hardhat Network, локальную сеть Ethereum, предназначенную для разработки. Она встроена в Hardhat и используется в качестве сети по умолчанию. Вам не нужно ничего настраивать, чтобы использовать ее.
В наших тестах мы будем использовать ethers.js для взаимодействия с контрактом Ethereum, который мы создали в предыдущем разделе, а в качестве программы запуска тестов мы будем использовать 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();
Signer
в ethers.js — это объект, представляющий счет Ethereum. Он используется для отправки транзакций контрактам и другим счетам. Здесь мы получаем список счетов в узле, к которому мы подключены, в данном случае это Hardhat Network, и сохраняем только первый.
Переменная ethers
доступна в глобальной области видимости. Если вы любите, чтобы ваш код всегда был явным, вы можете добавить эту строку сверху:
const { ethers } = require("hardhat");
const Token = await ethers.getContractFactory("Token");
Фабрика ContractFactory
в ethers.js — это абстракция, используемая для развертывания новых смарт-контрактов, поэтому Token
здесь — это фабрика для экземпляров нашего контракта с токенами.
const hardhatToken = await Token.deploy();
Вызов deploy()
на ContractFactory
начнет развертывание и вернет Promise
, который разрешается в Contract
. Это объект, который имеет метод для каждой функции вашего смарт-контракта.
const ownerBalance = await hardhatToken.balanceOf(owner.address);
Как только контракт развернут, мы можем вызвать методы нашего контракта на hardhatToken
. Здесь мы получаем баланс счета владельца, вызывая метод balanceOf()
контракта.
Напомним, что счет, который развертывает токен, получает весь его запас. По умолчанию экземпляры ContractFactory
и Contract
связаны с первым подписавшим. Это означает, что учетная запись в переменной owner
выполнила развертывание, и balanceOf()
должна вернуть всю сумму запаса.
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
Здесь мы снова используем наш экземпляр Contract
для вызова функции смарт-контракта в нашем коде Solidity. totalSupply()
возвращает сумму поставки токена, и мы проверяем, что она равна ownerBalance
, как и должно быть.
Для этого мы используем Chai, которая является популярной библиотекой утверждений JavaScript. Эти функции утверждения называются «матчеры», и те, которые мы используем здесь, взяты из плагина @nomicfoundation/hardhat-chai-matchers
, который расширяет Chai многими матчерами, полезными для тестирования смарт-контрактов.
— Использование другой учетной записи
Если вам нужно протестировать свой код, отправив транзакцию со счета (или Signer
в терминологии ethers.js), отличного от счета по умолчанию, вы можете использовать метод connect()
на объекте ethers.js Contract
, чтобы подключить его к другому счету, как показано ниже:
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);
});
});
— Повторное использование общих настроек тестов с помощью фикстур
Два теста, которые мы написали, начинаются с их настройки, что в данном случае означает развертывание контракта токена. В более сложных проектах эта настройка может включать в себя несколько развертываний и других транзакций. Выполнение этого в каждом тесте означает дублирование кода. Кроме того, выполнение множества транзакций в начале каждого теста может сделать набор тестов намного медленнее.
Вы можете избежать дублирования кода и повысить производительность вашего набора тестов, используя фикстуры. Фикстура — это функция настройки, которая выполняется только при первом вызове. При последующих вызовах, вместо повторного запуска, 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
— Контакт 联系方式
Домашняя страница : 565.ee
GitHub : github.com/565ee
Электронная почта : 565.eee@gmail.com
Facebook : facebook.com/565.ee
Twitter : twitter.com/565_eee
Telegram : t.me/ee_565