Testing Guide

이 장에서는 스마트 컨트랙트를 테스트하는 방법을 소개합니다. 블록체인의 트랜잭션은 되돌릴 수 없으므로 스마트 컨트랙트를 배포하기 전에 테스트하는 것이 매우 중요합니다.

트러플(Truffle)로 테스트하기

트러플은 자동 테스트 프레임워크를 제공합니다. 이 프레임워크를 사용하여 간단하고 관리 가능한 테스트를 작성할 수 있는 방법이 두 가지 있습니다.

  • JavascriptTypeScript를 이용하여 블록체인 외부에서 컨트랙트를 실행하는 애플리케이션처럼 작성

  • Solidity를 활용하여 컨트랙트 함수를 직접 호출

1) 시작하기

배포하기 전에 스마트 컨트랙트 테스트를 위해 값 설정 함수 setGreet 함수를 추가합니다. 소스 코드는 아래와 같습니다.

pragma solidity ^0.5.6;
contract Mortal {
    /* 주소 타입의 소유자(owner) 변수 정의 */
    address payable owner;
    /* 이 함수는 초기화 시점에 실행되어 컨트랙트 소유자를 설정합니다 */
    constructor () public { owner = msg.sender; }
    /* 컨트랙트에서 자금을 회수하는 함수 */
    function kill() public payable { if (msg.sender == owner) selfdestruct(owner); }
}

contract MEVerseGreeter is Mortal {
    /* 문자열 타입의 변수 greeting 정의 */
    string greeting;
    /* 이 함수는 컨트랙트가 실행될 때 작동합니다 */
    constructor (string memory _greeting) public {
        greeting = _greeting;
    }
    /* 주(Main) 함수 */
    function greet() public view returns (string memory) {
        return greeting;
    }

    /* 테스트를 위해 새로 추가된 함수입니다 */
    function setGreet(string memory _greeting) public {
        // 소유자(owner)만 greeting 메세지를 수정할 수 있습니다
        require(msg.sender == owner, "Only owner is allowed.");
        greeting = _greeting;
    }
}

테스트 시나리오

  1. greet() 함수가 "Hello, MEVerse"이라는 메세지를 잘 출력하는지

  2. setGreet() 함수가 새로 설정된 greeting 메세지를 잘 출력하고 소유자가 아닌 계정이 greeting을 업데이트하려고 할 때 revert를 하는지

먼저 일반적인 어설션(assertions)을 위해 Chai 어설션 라이브러리를 설치하고 (또는 사용하고 있는 다른 어설션 라이브러리도 괜찮습니다), 스마트 컨트랙트 어설션을 위해 truffle-assertions 라이브러리를 설치합니다.

npm install --save-dev chai truffle-assertions

2) 솔리디티로 테스트 작성하기

솔리디티로 테스트하는 것은 자바스크립트로 테스트하는 것보다 조금 더 직관적일 수 있습니다. 솔리디티 테스트 컨트랙트는 자바스크립트 테스트와 함께 .sol 파일로 제공됩니다.

test 폴더에 TestMEVerseGreeting.sol 이름의 파일을 생성합니다. 트러플 제품군은 테스트를 위한 헬퍼(helper) 라이브러리를 제공하므로, 이들을 불러옵니다. 솔리디티 테스트 예시를 살펴봅시다.

pragma solidity ^0.5.6;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/HashMarket.sol";
  • Assert : Assert.equals(), Assert.greaterThan() 등과 같은 다양한 테스트 함수에 액세스할 수 있도록 합니다.

  • DeployedAddresses : 컨트랙트를 변경할 때마다, 반드시 새 주소로 재배포해야 합니다. 이 라이브러리를 통해 배포된 컨트랙트 주소를 얻을 수 있습니다.

pragma solidity ^0.5.6;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MEVerseGreeter.sol";

contract TestMEVerseGreeting {

    function testGreetingMessage() public {
        // DeployedAddresses.MEVerseGreeter()는 컨트랙트 주소를 다룹니다.
        MEVerseGreeter greeter = MEVerseGreeter(DeployedAddresses.MEVerseGreeter());

        string memory expectedGreet = "Hello MEVerse";

        string memory greet = greeter.greet();

        Assert.equal(greet, expectedGreet, "greeting message should match");
    }
}

솔리디티 테스트 코드를 실행합니다.

$ truffle test
Using network 'test'.


Compiling your contracts...
===========================
> Compiling ./test/TestMEVerseGreeting.sol
> Artifacts written to /var/folders/8k/pj9b6lld4qn4hq1l7h97y9pm0000gn/T/test--5158-mS50YzRSpyUZ
> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang



  TestMEVerseGreeting
    1) testGreetingMessage
    > No events were emitted


  0 passing (3s)
  1 failing

  1) TestMEVerseGreeting
       testGreetingMessage:
     Error: greeting message should match (Tested: Hello, MEVerse, Against: Hello MEVerse)
      at checkResultForFailure (/Users/meverselabs/.nvm/versions/node/v16.16.0/lib/node_modules/truffle/build/webpack:/packages/core/lib/testing/SolidityTest.js:66:1)
      at runMicrotasks (<anonymous>)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at Context.<anonymous> (/Users/meverselabs/.nvm/versions/node/v16.16.0/lib/node_modules/truffle/build/webpack:/packages/core/lib/testing/SolidityTest.js:92:1)

테스트를 실패했습니다.

Error: greeting message should match (Tested: Hello, MEVerse, Against: Hello MEVerse)

배포컨트랙트는 Hello, MEVerse이고 테스트로 작성한 코드는 Hello MEVerse ',(comma)' 로 동일하지 않아서 테스트 실패하였습니다. 테스트 코드 수정(추가)후 테스트 해보겠습니다.

$ truffle test
Using network 'test'.


Compiling your contracts...
===========================
> Compiling ./test/TestMEVerseGreeting.sol
> Artifacts written to /var/folders/8k/pj9b6lld4qn4hq1l7h97y9pm0000gn/T/test--5231-DBNS4Mgo7Mo0
> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang



  TestMEVerseGreeting
     testGreetingMessage (41ms)


  1 passing (3s)

테스트가 성공적으로 통과 된 것을 확인합니다.

테스트넷에서 컨트랙트를 테스트하고 싶으면 network 옵션을 사용하면 됩니다.

$ truffle test -network MEVerseTestnet
Using network 'test'.


Compiling your contracts...
===========================
> Compiling ./contracts/MEVerseGreeter.sol
> Compiling ./test/TestMEVerseGreeting.sol
> Artifacts written to /var/folders/8k/pj9b6lld4qn4hq1l7h97y9pm0000gn/T/test--5324-jtF8M5Qz82cV
> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang



  TestMEVerseGreeting
     testGreetingMessage (55ms)


  1 passing (3s)

3) 자바스크립트로 테스트 작성하기

트러플은 자바스크립트 테스트를 위한 견고한 프레임워크를 제공하기 위해 Mocha 테스트 프레임워크 및 Chai 어설션 라이브러리를 사용합니다. 자바스크립트 테스트는 더 많은 유연성을 제공하며 더 복잡한 테스트를 작성할 수 있게 합니다.

test 폴더에 0_MEVerseGreeting.js 이름의 파일을 생성하고 다음의 테스트 코드를 추가합니다.

// MEVerseGreeter 컨트랙트와 직접 상호작용
const MEVerseGreeter = artifacts.require("./MEVerseGreeter.sol");
const truffleAssert = require('truffle-assertions');

contract("MEVerseGreeter", async(accounts) => {
    // 컨트랙트 인스턴스를 상위 레벨에 저장해
    // 모든 함수에서 접근할 수 있도록 합니다.
    var MEVerseGreeterInstance;
    var owner = accounts[0];
    var greetMsg = "Hello, MEVerse";

    // 각 테스트가 진행되기 전에 실행됩니다.
    before(async function() {
        // set contract instance into a variable
        MEVerseGreeterInstance = await MEVerseGreeter.new(greetMsg, {from:owner});
    })

    it("#1 check Greeting message", async function() {
        // set the expected greeting message
        var expectedGreeting = greetMsg;
        var greet= await MEVerseGreeterInstance.greet();
        assert.equal(expectedGreeting, greet, "greeting message should match");

    })

    it("#2 update greeting message.", async function() {
        var newGreeting = "Hi, MEVerse";

        await MEVerseGreeterInstance.setGreet(newGreeting, { from:owner });
        var greet = await MEVerseGreeterInstance.greet();
        assert.equal(newGreeting, greet, "greeting message should match");
    });

    it("#3 [Failure test] Only owner can change greeting.", async function() {
        var fakeOwner = accounts[1];        
        await truffleAssert.fails(MEVerseGreeterInstance.setGreet(greetMsg, { from:fakeOwner }));
    });
});

테스트 파일(0_MEVerseGreeting.js)을 지정하여 테스트를 실행합니다.

$ truffle test ./test/0_MEVerseGreeting.js
Using network 'test'.


Compiling your contracts...
===========================
> Compiling ./contracts/MEVerseGreeter.sol
> Artifacts written to /var/folders/8k/pj9b6lld4qn4hq1l7h97y9pm0000gn/T/test--5527-DYxpp0UInYnm
> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang



  Contract: MEVerseGreeter
     #1 check Greeting message
     #2 update greeting message. (42ms)
     #3 [Failure test] Only owner can change greeting. (176ms)


  3 passing (301ms)

테스트에 대한 자세한 내용은 Truffle testingTruffle commands을 참조하시길 바랍니다.

Last updated