728x90
[IT/Blockchain] - Hardhat 튜토리얼 - 1) 시작
[IT/Blockchain] - Hardhat 튜토리얼 - 2) 소스 분석
[IT/Blockchain] - Hardhat 튜토리얼 - 3) 배포 설정
컨트랙트 테스트
1) 무결성 검증
- 한번 배포된 컨트랙트는 수정이 불가능합니다
- 테스트를 통해 충분히 컨트랙트가 정상적으로 동작하는 확인 과정이 필요합니다.
2) 불필요한 비용 절약
- 메인넷에 배포를 하기 위해선 일정량의 가스비 + 테스트 트랜잭션 비용 등이 필요할 수 있습니다
- 로컬 테스트를 통해 배포 과정에서 발생하는 비용을 절약할 수 있습니다
3) 다양한 시나리오 테스트
- 다수의 유저가 사용할 때, 일정 시간이 흐르고 난 뒤의 동작이 어떻게 될 것인지?
- 다양한 상황에서도 컨트랙트가 의도한 대로 동작하는지 검증해볼 수 있습니다
테스트 스크립트 분석
- 테스트 초기 환경 설정: 컨트랙트 초기값 설정, signer(계정, 지갑) 생성, 컨트랙트 배포
- describe 대분류 테스트, it 소분류 테스트, expect 결과값 검증으로 작성됩니다.
- 각 테스트 별로 원하는 결과에 대해 설명을 작성하였습니다.
import {
time,
loadFixture,
} from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
import { expect } from "chai";
import { ethers } from "hardhat";
// describe는 테스트의 주제 구분입니다.
// describe 안에 describe 구분을 또 넣을 수 있습니다. (대주제, 소주제 같은)
describe("Lock", 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 snapshot in every test.
// 테스트를 위해 컨트랙트를 초기화 하는 부분입니다.
async function deployOneYearLockFixture() {
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
const ONE_GWEI = 1_000_000_000;
const lockedAmount = ONE_GWEI;
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
// Contracts are deployed using the first signer/account by default
// hardhat.config.js에 설정해 놓은 계정을 순서대로 가져옵니다.
const [owner, otherAccount] = await ethers.getSigners();
//Lock.sol 파일을 인스턴스로 가져옵니다.
const Lock = await ethers.getContractFactory("Lock");
// 컨트랙트를 배포합니다.
// 특별한 지정이 없으면 getSigners에서 가져온 계정 중 첫번째 계정으로 배포를 진행합니다.
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
//(참고)
// Lock.connect(otherAccount).deploy(..) 이렇게 사용하면 지정된 계정으로 배포를 하게 됩니다.
// 배포한 컨트랙트와 필요한 값을 리턴해놓습니다.
return { lock, unlockTime, lockedAmount, owner, otherAccount };
}
// 중간 주제인 Deployment를 위해 describe가 사용되었습니다.
describe("Deployment", function () {
//it는 실제 테스트를 하는 주제일 때 사용합니다.
it("Should set the right unlockTime", async function () {
//위에서 설정한 deployOneYearLockFixture를 hardhat에서 제공하는 loadFixture를 통해 초기화 합니다.
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);
// expect는 실제 테스트 입니다.
// 여기서는 unlockTime이 설정한 unlocTime과 동일한지 확인합니다.
expect(await lock.unlockTime()).to.equal(unlockTime);
});
// owner.address가 배포할 때 사용된 address가 동일한지 확인합니다.
it("Should set the right owner", async function () {
//매 번의 테스트 마다 다시 초기화를 진행해서 같은 환경에서 테스트가 되도록 하기 위함입니다.
// 각각의 it 사이에서 중복이 발생되지 않게 합니다.
const { lock, owner } = await loadFixture(deployOneYearLockFixture);
expect(await lock.owner()).to.equal(owner.address);
});
// lockedAmount가 배포할 때 사용한 금액이 동일한지 확인합니다.
it("Should receive and store the funds to lock", async function () {
const { lock, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
expect(await ethers.provider.getBalance(lock.target)).to.equal(
lockedAmount
);
});
// unlockTime은 배포 시점보다 미래의 시간이어야 합니다.
it("Should fail if the unlockTime is not in the future", async function () {
// We don't use the fixture here because we want a different deployment
const latestTime = await time.latest();
const Lock = await ethers.getContractFactory("Lock");
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
"Unlock time should be in the future"
);
});
});
// withdrawals 함수가 정상 동작하는지 테스트 주제입니다.
describe("Withdrawals", function () {
// unlockTime이 지나지 않은 시점에서 출금이 발생하지 않는지 확인합니다.
describe("Validations", function () {
it("Should revert with the right error if called too soon", async function () {
const { lock } = await loadFixture(deployOneYearLockFixture);
// unlockTime이 지나지 않은 시점에 출금을 하려하면 revert가 일어나야 합니다.
// 이 경우는 revert가 발생해야 통과되는 테스트입니다.
await expect(lock.withdraw()).to.be.revertedWith(
"You can't withdraw yet"
);
});
// owner가 아닌 계정으로 출금하려 하면 revert되어야 합니다.
it("Should revert with the right error if called from another account", async function () {
const { lock, unlockTime, otherAccount } = await loadFixture(
deployOneYearLockFixture
);
// We can increase the time in Hardhat Network
// Hardhat Network 상에서 시간이 unlockTime까지 지나도록 합니다.
await time.increaseTo(unlockTime);
// We use lock.connect() to send a transaction from another account
// unlockTime이 지났어도 owner가 아닌 다른 계정으로 출금을 할 수 없습니다.
// 이 경우는 revert가 발생해야 통과되는 테스트입니다.
await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
"You aren't the owner"
);
});
// owner 계정으로 unlockTime이 지난 후 출금을 하면 revert가 발생하지 않아야 합니다.
it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
const { lock, unlockTime } = await loadFixture(
deployOneYearLockFixture
);
// Transactions are sent using the first signer by default
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).not.to.be.reverted;
});
});
describe("Events", function () {
// owner 계정으로 unlockTime이 지난 후 출금을 하면 정상 출금이 되고 emit로 event log가 찍혀야 합니다.
it("Should emit an event on withdrawals", async function () {
const { lock, unlockTime, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw())
.to.emit(lock, "Withdrawal")
.withArgs(lockedAmount, anyValue); // We accept any value as `when` arg
});
});
describe("Transfers", function () {
// owner 계정으로 unlockTime이 지난 후 출금을 하면 정상 출금이 되고 lockedAmount 만큼 balance가 줄어야 합니다.
it("Should transfer the funds to the owner", async function () {
const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).to.changeEtherBalances(
[owner, lock],
[lockedAmount, -lockedAmount]
);
});
});
});
});
테스트 실행 (정상 실행 결과)
$ npx hardhat test test/Lock.ts
You're running a network fork starting from the latest block.
Performance may degrade due to fetching data from the network with each run.
If connecting to an archival node (e.g. Alchemy), we strongly recommend setting
blockNumber to a fixed value to increase performance with a local cache.
Lock
Deployment
✔ Should set the right unlockTime (2425ms)
✔ Should set the right owner
✔ Should receive and store the funds to lock
✔ Should fail if the unlockTime is not in the future (169ms)
Withdrawals
Validations
✔ Should revert with the right error if called too soon
✔ Should revert with the right error if called from another account
✔ Shouldn't fail if the unlockTime has arrived and the owner calls it
Events
✔ Should emit an event on withdrawals
Transfers
✔ Should transfer the funds to the owner
9 passing (3s)
728x90
'IT > Blockchain' 카테고리의 다른 글
ERC-20? ERC, EIP란 무엇인가? (0) | 2023.08.12 |
---|---|
블록체인에서의 니모닉(Mnemonic) (0) | 2023.08.10 |
Hardhat 튜토리얼 - 3) 배포 설정 (0) | 2023.08.10 |
Hardhat 튜토리얼 - 2) 소스 분석 (0) | 2023.08.09 |
Hardhat 튜토리얼 - 1) 시작 (0) | 2023.08.09 |