[DreamChain DApp] #9 Smart Contract Unit Test 2

in #kr7 years ago (edited)

이번에도 Unit test 입니다. 달려보겠습니다.

이전글 - [DreamChain DApp] #8 Smart Contract Unit Test 1

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


이전글에서 contribute함수의 테스트를 진행했습니다. 함수가 매우 간단하여, 더 진행할 테스트는 생각이 안납니다 ^^; 스마트 컨트랙트는 매우 간결하고 알기 쉽게 작성되어야 합니다! 왜냐구요? 복잡하면 사람들이 신뢰할 수 없고, 비용 소모도 크기 때문입니다!

이어서 다음 함수를 테스트 해보겠습니다.

download 함수 테스트

download함수 테스트를 위해서는 이전글에서 진행한 contribute 함수 호출이 필요합니다. 왜냐하면 download는 contributor만 가능하기 때문입니다. 따라서 이전글에서 진행한 코드를 중복해서 사용하겠습니다. 즉, 새롭게 describe 테스트 블락을 만듭니다. 테스트할 내용은 다음과 같습니다.

  • downloads수는 증가하는가?
  • contribute만 download 함수를 실행할 수 있는가?
  • 이미 다운로드한 contributor가 다시 다운로드하는 것을 막아주는가?
  • 다운로드한 contributor는 downloads 배열에 저장되는가?
  • contributor가 최소 다운로드 금액보다 작게 입력하면 다운로드는 실패하는가?

[ 컨트랙트 코드변경]

테스트 항목을 생각하다 보니 스마트 컨트랙트의 소스코드에 빠진 부분을 발견했습니다.
download 함수가 실행되면 contributor가 설정한 금액이 컨트랙트로 전송되어야 합니다. 스마트 컨트랙트 소스 코드에서 download 함수를 아래와 같이 변경시켜 주세요.

    /*
     * Download (license of) the dream story
     * @note this function can receive some money, which is msg.value
     */
    function download() public payable onlyContributor {
        // check if the contributor has downloaded before.
        // if so, no need to download again
        require( !downloaders[msg.sender] );
        // check if the input price is bigger than the min_down_price_wei
        require( msg.value >= min_down_price_wei );
        // local variable is basically stored in storage,
        // and literal such as struct is created in memory since it is temporary.
        // storage variable references the original variable
        // memory variable copies the original variable
        // memory is temporary, storage is global.
        Download memory new_download= Download({
           downloader: msg.sender,
           price_wei: msg.value,
           date: now
        });

        // add it to the downloads array
        downloads.push( new_download );
        // set the download address to true
        downloaders[ msg.sender ]= true;
    }

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

$ cd ethereum
$ node compile.js

1. contributor 아닌 계정으로 download 시도

먼저 contributor가 아닌 계정으로 다운로드를 시도해 보는 테스트입니다. 당연히 오류가 발생해야 합니다. 테스트 코드에서는 오류가 발생하면 테스트가 성공하는 방식으로 작성되었습니다. 헷갈리지 마세요~

// test download value in ether
const INIT_DOWNLOAD_ETH= '2';
// test groups: download tests
describe( 'DreamStory download function tests', () => {
  //// test: try to download using a not contributor's account
  it( 'Downloader test', async () => {
    // call download using account[2] which is not a contributor
    try {
      await dream_story.methods.download().send( {
        from: accounts[2],
        // amount to download. convert ether to wei
        value: web3.utils.toWei( INIT_DOWNLOAD_ETH, 'ether' ),
        // gas limit to execute this function
        gas: 1000000
      });
      assert( false );
    }
    catch( error ) {
      // if error occurs, this assert will pass, which is intended
      assert( true );
    }
  });
  • contribution하지 않은 accounts[2] 계정으로 download 함수를 호출했을 때 try -catch 문에서 에러가 발생
  • 테스트는 assert( true )에 의해 pass
  • [주의] download 함수 호출 할때, gas를 명시하지 않으면 out of gas 오류 발생할 수 있음. gas를 명시하지 않으면 gas limit(해당 함수를 수행할 때 max로 지불하고자 하는 gas량)을 자동으로 계산하나, 이경우는 그 계산값이 틀렸는지 out of gas에러를 발생하였음.
  • 이전 contribute 함수 테스트할 때는 gas를 명시하지 않았는데, 명시하는 것이 확실함.
  • 테스트 결과: 생략

2. download count, double download, downloader

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

  • 테스트1: download 함수 실행 후 download 수가 증가
  • 테스트2: download 계정이 downloaders에 추가
  • 테스트3: 이미 download한 계정이 다시 download
  • 테스트4: contributor가 minimum download price보다 적게 송금

참고로 1번의 테스트 블락(describe)내에 아래 코드가 존재해야 합니다. 테스트 블락 내에서 개별적으로 수행될 수 있는 테스트는 'it'으로 분리시켰습니다. 분리해도 아래 코드는 꽤 깁니다. 테스트를 진행하기 위해 사전에 필요한 것들이 많기 때문입니다.

  ////// tests: increment of the number of downloads
  //            downloaders array
  //            double download
  //            minimum download price
  it( 'Check downloads count and others', async () => {
    // get the intial balance of the c to test balance
    let init_balance= await web3.eth.getBalance( dream_story.options.address );
    // convert to ether
    init_balance= web3.utils.fromWei( init_balance, 'ether' );
    // cast string to float
    init_balance= parseFloat( init_balance );
    // print out initial balance
    console.log( init_balance );
    // 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 downloads
    // call download using a contributor's account
    await dream_story.methods.download().send( {
      from: accounts[1],
      // amount to download. convert ether to wei
      value: web3.utils.toWei( INIT_DOWNLOAD_ETH, 'ether' ),
      // gas limit to execute this function
      gas: 1000000
    });
    // get the summary
    const summary= await dream_story.methods.getSummary().call();
    // check if the number downloads is increased
    assert.equal( summary[2], 1 );
    // print out the votes_count
    console.log( summary[2] );

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

    //// test: block double downloading
    // call download using the address of a downloader
    try {
      await dream_story.methods.download().send( {
        from: accounts[1],
        // amount to download. convert ether to wei
        value: web3.utils.toWei( INIT_DOWNLOAD_ETH, 'ether' ),
        // 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 );
    }

    //// test: minimum download price`
    // call download with less amount of money than the miminum
    try {
      const min_down_price= await dream_story.methods.min_down_price_wei().call();
      const down_price= min_down_price - 10;
      await dream_story.methods.download().send( {
        from: accounts[1],
        // amount to download. convert ether to wei
        value: down_price,
        // 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 );
    }
  })
}); // end of describe( 'DreamStory download function tests' ... )
  • download한 계정의 balance 변화는 테스트 하지 않음
  • accounts[1]으로 contribute함수를 먼저 실행하고, 이후에 download 함수 실행. 결과값을 얻기 위해 getSummary 함수 사용.
  • 테스트1: downloads의 개수는 getSummary의 리턴값 중 3번째, 즉 index로는 2인 값을 읽어서 비교
  • 테스트2: download한 accounts[1]계정이 downloaders에 추가되었는지 체크
  • 테스트3: accounts[1]로 download한 상태에서 또다시 download 호출. try-catch의 error 발생
  • 테스트4: accounts[1]로 minimum보다 10wei 작게 입력하여 download 호출. try-catch의 error 발생.
  • 테스트 결과
    • 첫번째 0은 컨트랙트의 초기 balance
    • 두번째 1은 downloads 수
    • 세번째 true는 accounts[1]가 downloaders에 속함
    • 네번째 true는 중복 download 방지 성공
    • 다섯번째 true는 최소 금액이하로 download 금지 성공
    • try-catch문에서 assert( error )는 error가 발생하여 error 객체가 존재하기 때문에 assert( error )값은 true가 됨.

스마트 컨트랙트 코딩을 성급하게 했는지, 몇가지 오류가 있어서 테스트 하면서 수정을 좀 했습니다. 연재를 보시는 분은 참고하시길 부탁드립니다. 역시나 테스트가 왜 필요한지 절실히 느끼는 글이 됐습니다. 매우 짧아 보이는 테스트이지만 오류 수정과 개념 정립하느라 3-4시간이 훌쩍 지나갔네요.

그럼 다음에는 나머지 함수들 테스트를 해보겠습니다. 어째 다음 것들도 수정이 필요할 거 같네요.


오늘의 실습: 스마트 컨트랙트 코딩을 먼저 하지 않고, 테스트를 먼저 구상하면서 컨트랙트를 디자인 해보세요. 이 방식이 test driven development coding 방식입니다.

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.33
JST 0.034
BTC 110675.01
ETH 4295.01
USDT 1.00
SBD 0.83