[DreamChain DApp] #10 Smart Contract Unit Test 3

in #kr7 years ago (edited)

오늘도 달립니다.

이전글 - [DreamChain DApp] #9 Smart Contract Unit Test 2

본 내용은 Ethereum and Solidity: The Complete Developer's Guide을 참고해서 작성되었습니다.


아직 테스트하지 못한 함수들이 몇개 있습니다. 테스트를 진행하며 코드가 추가되거나 변경될 수 있습니다. Test Driven Development 코딩 방식이 개발시간을 단축시켜준다는 말은 많이 봐왔는데, 이렇게 차근히 테스트를 바탕으로 코딩을 진행하니 정말 코드가 탄탄해지고, 나중에 발생할오류와 버그가 현저히 줄거 같은 기분이 들어 좋습니다!

approveWithdrawal 함수 테스트

approveWithdrawal함수는 contributor가 컨트랙트에 누적된 금액을 인출하는 것에 동의하는 함수입니다. contributor 각각이 별도로 실행하는 함수입니다. 이 함수에 의해 코인이 전송되는 것은 아닙니다. 어떤 contributor가 인출에 동의했는지, 동의한 사람은 몇 명인지를 관리합니다. 테스트할 내용은 다음과 같습니다.

  • 함수를 호출한 계정은 contributor인가?
  • 함수를 호출한 계정은 이미 approve했는가?
  • approve를 하면 approvals_count는 증가하는가?
  • approve를 하면 해당 계정은 approvals 리스트에 등록되는가?

어떤 것을 테스트할 것인가는 전적으로 개발자가 정해야 합니다. 조금이라도 코드를 확인하고 싶은 것이 있다면 테스트에 추가해야 합니다.

[컨트랙트 코드변경]

이번에도 몇가지 스마트 컨트랙트 소스코드를 변경합니다. 단순히 상태 변수들 이름을 바꿨습니다. 왜 굳이 이름을 바꾸냐 하실 수도 있는데, 코드를 봤을 때 변수가 의미하는 바를 바로 알 수 있게끔해야 좋은 코드입니다. 이전에 승인자의 개념으로 approvals이란 변수명을 썼는데, 이것은 승인자의 계정이란 정보를 의미하지 않아서, 분명하게 approvers라고 하여 계정을 의미하도록 바꿨습니다. 아래와 같이 코드를 수정 합니다. 수정 부분은 상태변수 선언부와 함수입니다.

// list of approvers
 mapping( address => bool ) public approvers;
 // number of approvers
 uint public approvers_count;

 (중간 생략)

function approveWithdrawal() public onlyContributor {
  // check whether this contributor approved the withdrawl already
   require( !approvers[msg.sender] );
   // set the account as an approver
   approvers[ msg.sender ]= true;
   // increase the counts
   approvers_count++;
}

컨트랙트 코드를 변경하면 반드시 compile.js 스크립트를 실행해야 합니다. 아래와 같이 compile.js가 위치한 곳으로 이동하여 스크립트를 실행합니다.

$ cd ethereum
$ node compile.js

또 한가지 오류를 발견했습니다! 바로 compile.js 파일 자체입니다. 컨트랙트 소스를 변경하고 compile.js를 호출했는데, 여기서 컴파일 결과가 표시되지 않았습니다. 당연히 제대로 된줄 알았는데, 코드의 오류로 compile이 제대로 안되었지만, 어떤 메시지도 띄우지 않았습니다. Remix에 코드를 붙여 넣었다면 오류를 알 수 있었겠지만, 별도의 compile.js를 사용하는데 있어서는 컴파일 실패를 알려주지 않는 문제를 가지고 있습니다. 그래서 compile.js 파일에 아래와 같이 추가합니다.

// import assert module to test with assertion
const assert= require( 'assert' );

(중간 생략)

// compiled output, extract only contracts part
console.log( 'now compiling the contract code ...' );
const compile_output= solc.compile( contract_src, 1 ).contracts;
assert( compile_output[':DreamStory'] );
console.log( 'compiled' );

컴파일이 실패하면 assert 문에서 잡아낼 것입니다. assert 인자로 쓰인 :DreamStory는 contract 이름입니다. 주의할 것은 앞에 :가 붙어 있는 점입니다. 이것은 솔리디티 컴파일러에 의해 만들어진 것이라 이렇게 해줘야 합니다. 이렇게 해도 컴파일 실패했는지만 알고, 어디서 실패했는지는 알 수 없습니다. 따라서 Remix로 코드를 복사해서 코드 오류를 살펴봐야 합니다.

1. contributor가 아닌 계정으로 approveWithdrawal 실행

먼저 contributor가 아닌 계정으로 다운로드를 시도해 보는 테스트입니다. 당연히 오류가 발생해야 합니다. 테스트 코드에서는 오류가 발생하면 테스트가 성공하는 방식으로 작성되었습니다.
아래와 같이 새롭게 테스트 블락을 만듭니다. 내용은 download 함수 테스트와 동일합니다.

// test groups: approveWithdrawal tests
describe( 'DreamStory approveWithdrawal function tests', () => {
  //// test: try to approve using a not contributor's account
  it( 'approver test', async () => {
    // call approveWithdrawal using account[2] which is not a contributor
    try {
      await dream_story.methods.approveWithdrawal().send( {
        from: accounts[2],
        // gas limit to execute this function
        gas: 1000000
      });
      assert( false );
    }
    catch( error ) {
      // if error occurs, this assert will pass, which is intended
      assert( error );
      console.log( 'true' );
    }
  });
});
  • contribution하지 않은 accounts[2] 계정으로 approveWithdrawal 함수를 호출했을 때 try -catch 문에서 에러가 발생
  • 이 함수는 상태변수의 값을 바꾸기 때문에 gas가 소모됨. 따라서 적절한 gas limit을 입력해야 함.
  • 테스트는 assert( error )에 의해 pass. error object가 존재하기 때문에 assert 결과는 true가 됨.
  • 테스트 결과: 생략

2. approvers_count, double approvals, approvers 등록 테스트

다음 세가지 테스트를 동시에 진행해보겠습니다.

  • 테스트1: approveWithdrawal 함수 실행 후 approvers_count 수가 증가
  • 테스트2: approve한 계정이 approvers에 추가
  • 테스트3: 이미 approver가 다시 approve 수행

참고로 1번의 테스트 블락(describe)내에 아래 코드가 존재해야 합니다. 테스트 블락 내에서 개별적으로 수행될 수 있는 테스트는 'it'으로 분리시켰습니다.

////// tests: increment of the number of approvers
  //            approvers list
  //            double approvals
  it( 'Check approvers count and others', async () => {
    // use the second account to contribute
    await dream_story.methods.contribute().send( {
      from: accounts[1],
      // amount to contribute. convert the ether into wei
      value: web3.utils.toWei( INIT_CONTRIBUTE_ETH, 'ether' ),
      // gas limit to execute this function
      gas: 1000000
    });

    //// test: increment of the number of approvers
    // call approveWithdrawal using a contributor's account
    await dream_story.methods.approveWithdrawal().send( {
      from: accounts[1],
      // gas limit to execute this function
      gas: 1000000
    });
    // get the summary
    const summary= await dream_story.methods.getSummary().call();
    // check if the number of approvers is increased
    assert.equal( summary[4], 1 );
    // print out the approvers_count
    console.log( summary[4] );

    //// test: approver check
    // get the mapping value of the account
    const is_approver= await dream_story.methods.approvers( accounts[1] ).call();
    // assert the result
    assert( is_approver );
    console.log( is_approver );

    //// test: block double approvals
    // call approveWithdrawal using the approver's account
    try {
      await dream_story.methods.approveWithdrawal().send( {
        from: accounts[1],
        // gas limit to execute this function
        gas: 1000000
      });
      assert( false );
    }
    catch( error ) {
      // if error occurs, this assert will pass, which is intended
      assert( error );
      console.log( 'true' );
    }
  });
  • accounts[1]으로 contribute함수를 먼저 실행하고, 이후에 approveWithdrawal 함수 실행. 결과값을 얻기 위해 getSummary 함수 사용.
  • 테스트1: approvers의 개수는 getSummary의 리턴값 중 5번째, 즉 index로는 4인 값을 읽어서 비교
  • 테스트2: approve한 accounts[1]계정이 approvers에 추가되었는지 체크
  • 테스트3: accounts[1]로 approve한 상태에서 또다시 approveWithdrawal 호출. try-catch의 error 발생
  • 테스크 결과:
    • 첫번째 1은 approvers 수
    • 두번째 true는 accounts[1]가 approvers에 속함
    • 세번째 true는 중복 approveWithdrawal 방지 성공

테스트도 어느덧 마무리 단계입니다. 이제 다음글에서 테스트를 마치면 드디어 겉으로 들어나느 웹 페이지로 들어갑니다. 웹은 제 전문 분야가 아니지만 여기 저기 찾아가며 재밌게 만들어 보려고 합니다. 그럼 다음에 만나요~

오늘의 실습: 여러분이 작성한 코드의 변수명은 의미하는 바가 제대로 담겨있는지 확인해보세요. 간결성을 위해 의미를 포기한건 아닌지 살펴보세요.

Sort:  

(jjangjjangman 태그 사용시 댓글을 남깁니다.)
[제 0회 짱짱맨배 42일장]4주차 보상글추천, 1,2,3주차 보상지급을 발표합니다.(계속 리스팅 할 예정)
https://steemit.com/kr/@virus707/0-42-4-1-2-3

4주차에 도전하세요

그리고 즐거운 스티밋하세요!

Coin Marketplace

STEEM 0.13
TRX 0.34
JST 0.034
BTC 110816.67
ETH 4393.20
SBD 0.82