체인의정석

hardhat 사용법 정리 03- 테스트코드 작성하기 본문

블록체인/Ethers & web3

hardhat 사용법 정리 03- 테스트코드 작성하기

체인의정석 2022. 1. 18. 19:02
728x90
반응형

이제 배포를 했으니 테스트코드를 작성하고 돌려봐야 한다.

테스트 코드는 딱히 가나슈랑 다른 점은 없어보여서 그대로 가져와서 사용해보기로하였다.

 

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

describe("Greeter", function () {
  it("Should return the new greeting once it's changed", async function () {
    const Greeter = await ethers.getContractFactory("Greeter");
    const greeter = await Greeter.deploy("Hello, world!");
    await greeter.deployed();

    expect(await greeter.greet()).to.equal("Hello, world!");

    const setGreetingTx = await greeter.setGreeting("Hola, mundo!");

    // wait until the transaction is mined
    await setGreetingTx.wait();

    expect(await greeter.greet()).to.equal("Hola, mundo!");
  });
});

chai 모듈과 ethers를 사용하여 테스트를 하는것인데 나도 이대로 하려고 한다. 

expect를 걸어두는 부분이나 함수를 변수화하여 비동기화 시키는 작업이 필요한것 같다.

 

https://docs.openzeppelin.com/test-helpers/0.5/

 

Test Helpers - OpenZeppelin Docs

Assertion library for Ethereum smart contract testing. Make sure your contracts behave as expected!

docs.openzeppelin.com

오픈제플린의 공식 ERC 코드를 살펴보다가 test helper라는 모듈을 사용하고 있음을 발견하였다.

 

https://docs.openzeppelin.com/test-environment/0.1/

 

Test Environment - OpenZeppelin Docs

Blazing fast smart contract testing. One-line setup for an awesome testing experience. Near-instant start up: have your code running in under 2s after typing npm test. Test runner agnostic – from the familiarity of Mocha, to parallel tests using Jest or

docs.openzeppelin.com

또한 테스트 환경에 대한 모듈도 사용이 가능하다.

 

그러나 트러플과 하드햇의 환경이 혼동되어서 문서를 찾아보니 깔끔하게 정리된 페이지가 있었다. 

위 3개의 페이지를 참고하여 테스트코드를 작성할 예정이다.

https://docs.openzeppelin.com/learn/writing-automated-tests?pref=hardhat 

 

Writing automated smart contract tests - OpenZeppelin Docs

You may be wondering how we’re going to run these tests, since smart contracts are executed inside a blockchain. Using the actual Ethereum network would be very expensive, and while testnets are free, they are also slow (with blocktimes between 5 and 20

docs.openzeppelin.com

먼저 chai를 테스트 모듈로 사용한다.

npm install --save-dev chai

test 디렉토리에 테스트 코드를 관리하게 되는데 contracts 디렉토리에 있는 것을 미러링 하여 테스트 디렉토리에 그대로 넣어 주어야 한다. test 디렉토리는 루트 경로에 있어야 하며 .js 파일 형태로 만든다.

 

// test/Box.test.js
// Load dependencies
const { expect } = require('chai');

// Start test block
describe('Box', function () {
  before(async function () {
    this.Box = await ethers.getContractFactory('Box');
  });

  beforeEach(async function () {
    this.box = await this.Box.deploy();
    await this.box.deployed();
  });

  // Test case
  it('retrieve returns a value previously stored', async function () {
    // Store a value
    await this.box.store(42);

    // Test if the returned value is the same one
    // Note that we need to use strings to compare the 256 bit integers
    expect((await this.box.retrieve()).toString()).to.equal('42');
  });
});

 

이런 식으로 테스트 코드를 작성하면 되는데, 아래 링크를 공부해보라고 나와있었다.

https://github.com/MolochVentures/moloch/tree/4e786db8a4aa3158287e0935dcbc7b1e43416e38/test#moloch-testing-guide

 

GitHub - MolochVentures/moloch: 👹 Moloch whose mind is pure machinery! Moloch whose blood is running money!

👹 Moloch whose mind is pure machinery! Moloch whose blood is running money! - GitHub - MolochVentures/moloch: 👹 Moloch whose mind is pure machinery! Moloch whose blood is running money!

github.com

여기 글을 보면 반복되는 부분에 대해서 EVM snapshot과 revert를 사용하여 테스트를 빠르게 하며 반복을 줄인다고 한다.

  beforeEach(async () => {
    snapshotId = await snapshot()

    proposal1 = {
      applicant: applicant1,
      tokenTribute: 100,
      sharesRequested: 1,
      details: 'all hail moloch'
    }

    token.transfer(summoner, initSummonerBalance, { from: creator })
  })

  afterEach(async () => {
    await restore(snapshotId)
  })

테스트 코드에 자꾸 beforeEach가 나와 있는데 이러한 부분은 각 유닛테스트시 작동하게 된다. afterEach는 유닛테스트 이후에 작동하게 된다. 위의 코드에서 beforeEach를 사용하여 글로벌 변수들을 예측 가능한기본선으로 세팅해주게 된다. 이러한 each 구문은 describe의 블록 범위 앞뒤에서 트리거가 되게 된다. 또한 더 높은 스코프에서의 Each는 해당 범위를 모두 포괄하여 작동하게 된다.

 

foreEach_1 -> beforeEach_2 -> { test } -> afterEach

  describe('submitProposal', () => {
    beforeEach(async () => {
      await token.transfer(proposal1.applicant, proposal1.tokenTribute, {
        from: creator
      })
      await token.approve(moloch.address, 10, { from: summoner })
      await token.approve(moloch.address, proposal1.tokenTribute, {
        from: proposal1.applicant
      })
    })

beforeEach는 submitProposal의 단위 테스트를 위하여 토큰을 보내고 어프루브를 받아서 디파짓이 매번 가능하도록 만들어 준다.

이렇게 반복되는 기능은 매 테스트마다 계속해서 사용하게 된다. 

    it('happy case', async () => {
      await moloch.submitProposal(
        proposal1.applicant,
        proposal1.tokenTribute,
        proposal1.sharesRequested,
        proposal1.details,
        { from: summoner }
      )
      await verifySubmitProposal(proposal1, 0, summoner, {
        initialTotalShares: 1,
        initialApplicantBalance: proposal1.tokenTribute,
        initialProposerBalance: initSummonerBalance
      })
    })

위의 테스트가 있다고 쳐보고

    it('require fail - uint overflow', async () => {
      proposal1.sharesRequested = _1e18
      await moloch
        .submitProposal(
          proposal1.applicant,
          proposal1.tokenTribute,
          proposal1.sharesRequested,
          proposal1.details,
          { from: summoner }
        )
        .should.be.rejectedWith('too many shares requested')
    })

아래의 테스트가 반복되는 경우 단 하나만 바뀌게 된다. 오버 플로우를 테스트하기 위해 이처럼 반복되는 코드가 많이 나오게 된다.

 

이런 부분을 해결하기 위해 before Each를 달아서 중복되는 부분을 한번에 처리할 수 있다.

 

이번 부분은 중요하기 때문에 다음 글에서 이어서 정리하도록 하겠다.

728x90
반응형
Comments