[DreamChain DApp] #11 Smart Contract Unit Test 4
이번글로 Unit Test는 마무리 짓습니다. 야호~
이전글 - [DreamChain DApp] #10 Smart Contract Unit Test 3
본 내용은 Ethereum and Solidity: The Complete Developer's Guide을 참고해서 작성되었습니다.
Unit Test는 처음에 시작하기가 매우 어렵습니다. 왜냐면 빨리 작성한 코드를 돌려 보고 싶고, 웹페이지를 만들어 보고 싶거든요. 그러나 일단 테스트를 진행하게 되면 한결 마음이 편해집니다. 여러 곳에서 문제가 발생하니까요. 전문 코더들은 테스트 과정을 필수로 거칠 것입니다.
이제 마지막 테스트입니다. 어쩌면 제가 빼먹은 함수가 있어서 나중에 다시 테스트해야 할수도 있겠네요. 테스트를 반복하니깐 테스트 코드 작성하는 시간도 많이 줄어듭니다!
[컨트랙트 코드변경]
테스트 하기전에 코드를 또 살짝 변경합니다. 함수 이름 finalizeWithdrawal
에서 executeWithdrawal
을 변경합니다. 다시 말씀드리지만, 변수 이름뿐만 아니라 함수 이름도 의미를 충분히 전달해 주는게 좋습니다. finalize라고 하면 왠지 더이상 인출은 할 수 없는 의미라서, author가 주기적으로 인출할 수 있기 때문에 의미상 execute가 더 정확하다고 생각했습니다. 그리고 이전글에서 변수 이름을 수정한 것도 이 함수에 반영시킵니다.
/*
* Execute the withdrawal by the author
*/
function executeWithdrawal() public onlyAuthor {
// half of contributors should approve the withdrawal
require( approvers_count > ( votes_count/2 ) );
// transfer the balance to the author
author.transfer( address(this).balance );
}
컨트랙트 코드를 변경하면 반드시 compile.js 스크립트를 실행해야 합니다. 아래와 같이 compile.js가 위치한 곳으로 이동하여 스크립트를 실행합니다.
$ cd ethereum
$ node compile.js
executeWithdrawal 함수 테스트
executeWithdrawal함수는 author가 approvers의 동의하에 컨트랙트에 누적된 금액을 인출하여 자신의 계정에 전송하는 기능을 수행합니다. 여기서는 contributors의 절반이 넘는 approvals을 받았을 때만, 인출하도록 설정했습니다. 테스트할 것은 다음과 같습니다.
- author만 함수를 실행할 수 있는가?
- approvers_count가 절반이 넘을 때만 함수가 실행되는가?
- 컨트랙트에서 author 계정으로 누적 금액이 전송되는가?
1. author가 아닌 계정으로 함수 호출 시도
먼저 author가 아닌 계정(accounts[2])으로 인출을 시도해 보는 테스트입니다. 함수의 modifier로 인해 당연히 오류가 발생해야 합니다. 테스트 코드에서는 오류가 발생하면 테스트가 성공하는 방식으로 작성되었습니다. 아래와 같이 새롭게 테스트 블락을 만듭니다.
// test groups: executeWithdrawal tests
describe( 'DreamStory executeWithdrawal function tests', () => {
//// test: try to execute the withdrawal using no author's account
it( 'author test', async () => {
// call executeWithdrawal using account[2] which is not the author
try {
await dream_story.methods.executeWithdrawal().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' );
}
});
});
- author가 아닌 accounts[2] 계정으로 executeWithdrawal 함수를 호출했을 때 try -catch 문에서 에러가 발생
- 이 함수는 상태변수의 값을 바꾸기 때문에 gas가 소모됨. 따라서 적절한 gas limit을 입력해야 함.
- 테스트는 assert( error )에 의해 pass. error object가 존재하기 때문에 assert 결과는 true가 됨.
- 테스트 결과: 생략
2. 인출하기 위한 approvals 수와 balance transfer
다음 두가지 테스트를 진행합니다. 최종적으로 컨트랙트의 잔액을 인출하는 함수기 때문에 사전 작업이 많이 필요합니다.
- 테스트1: approvals이 절반인 상태에서 인출 시도
- 테스트2: author 계정의 balance 증가 확인
참고로 1번의 테스트 블락(describe)내에 아래 코드가 존재해야 합니다. 테스트 블락 내에서 개별적으로 수행될 수 있는 테스트는 'it'으로 분리시켰습니다.
////// tests: not enough approvals
// contract's balance tranfer
it( 'Check number of approvals and balance transfer', async () => {
// get the intial balance of the author, which is accounts[0]
let init_balance= await web3.eth.getBalance( accounts[0] );
// convert to ether
init_balance= web3.utils.fromWei( init_balance, 'ether' );
// cast string to float
init_balance= parseFloat( 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
});
// approve by the second account
await dream_story.methods.approveWithdrawal().send( {
from: accounts[1],
// gas limit to execute this function
gas: 1000000
});
// use the third account to contribute
await dream_story.methods.contribute().send( {
from: accounts[2],
// amount to contribute. convert the ether into wei
value: web3.utils.toWei( INIT_CONTRIBUTE_ETH, 'ether' ),
// gas limit to execute this function
gas: 1000000
});
// no approval by the third account
// get the summary
const summary= await dream_story.methods.getSummary().call();
// print out the votes_count
console.log( summary[1] );
// print out the approvers_count
console.log( summary[4] );
//// test: not enough number of approvals
// call executeWithdrawal using the author account
try {
await dream_story.methods.executeWithdrawal().send( {
from: accounts[0],
// 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: balance transfer
// approve by the thirde account
await dream_story.methods.approveWithdrawal().send( {
from: accounts[2],
// gas limit to execute this function
gas: 1000000
});
// get the contract balance before withdrawal
const contract_balance= await web3.eth.getBalance( dream_story.options.address );
// execute the withdrawal by the author
await dream_story.methods.executeWithdrawal().send( {
from: accounts[0],
// gas limit to execute this function
gas: 1000000
});
// get the balance of the author, which is accounts[0]
let balance= await web3.eth.getBalance( accounts[0] );
// convert to ether
balance= web3.utils.fromWei( balance, 'ether' );
// cast string to float
balance= parseFloat( balance );
assert( balance > init_balance );
// print out contract's balance before withdrawal
console.log( web3.utils.fromWei( contract_balance, 'ether' ) );
// print out initial balance
console.log( init_balance );
// print out the balance
console.log( balance );
});
- accounts[1], accounts[2]로 각각 contribute 실행후 accounts[1]만 approveWithdrawal 실행
- 테스트1: contributors수 2, approvals수 1로, 절반을 넘는 동의를 얻지 못한 상태에서 함수 실행 체크
- 테스트2: accounts[2]도 approve한 후, author의 balance 증가 체크
- 테스트 결과:
- 첫번째 2는 contributors 수
- 두번째 1은 approvers수
- 세번째 true는 try-catch 문에서 approvers 수 부족에 따른 error 발생 (의도한 것이 기 때문에 true)
- 네번째 2는 balance transfer 전의 컨트랙트 잔액 (ether)
- 다섯번째 99.9... 는 컨트랙트 balance transfer 전의 author 잔액 (ether)
- 여섯번째 101.8...는 컨트랙트 balance transfer 후의 author 잔액 (ether)
- author가 executeWithdrawal 함수를 실행하는데 약간의 gas가 소모되어 정확히 2 ether가 전송되지 않은 것임
이로써 의도한 DreamStory 컨트랙트의 동작을 테스트 해봤습니다. 한가지 테스트 하지 않은 함수가 있는데, getSummary 함수인데, 이 함수는 상태변수들의 값을 단순히 리턴하는 것이고, 이미 테스트 코드에 많이 사용되어 검증 되었습니다.
그럼 이제 웹페이지 구성으로 들어갑니다....
그런데 가만히 생각해 보면 DreamStory 컨트랙트는 뭔가 좀 이상합니다.
오늘의 실습: author가 여러명 존재할텐데, 여러 authors는 어떻게 관리할 수 있을까요? 연관해서 DreamStory 생성자 함수는 테스트 하지 않았습니다. 이건 누가 호출해야 할까요?