이전글에서 자동으로 컴파일하는 스크립트를 만들었습니다. 컴파일 스크립트에 대해서 자세히 설명은 안했지만, 주석을 보면 대충 뭐하는 코드인지 감은 올 것입니다.
이번에는 컴파일된 bytecode를 로컬 테스트 네트워크에 deploy하고 컨트랙트 기능을 테스트 하는 테스트 파일을 설명하겠습니다. deploy를 자동으로 하는 스크립트는 좀 나중으로 미루겠습니다. 이때는 로컬 네트워크에 deploy하지 않고, Rinkby와 같은 테스트 네트워크에 deploy하는 방법으로 설명할 것입니다. 이 때, 몇가지 개념적으로 알아야 할게 있어서 나중으로 미룹니다.
1. 로컬 테스트 네트워크와 Web3
로컬 이더리움 네트워크 Ganache에 deploy된 컨트랙트에 접속하기 위해서는 몇 가지가 필요합니다.
위 그림을 보면, Web3라는 constructor 함수가 있어서 이것을 객체화(instantiation)하면 web3라는 객체가 만들어집니다. 이 web3로 로컬 테스트 네크워크(Ganache)에 접속하려면 위 그림처럼 Provider가 필요합니다. 이것은 네트워크와 web3를 연결시켜 주는 연결 통로(Communication Layer)로 생각하면 됩니다. 이 Provider는 테스트 네트워크에 따라 달라집니다. 그래서 그림에서도 web3 인스턴스에 서로 다른 Provider가 붙을 수 있도록 표시되어 있습니다. 나중에 Rinkby 테스트 네트워크와 연결할 때는 다른 Provider가 web3와 붙을 것입니다.
참고로, 로컬 테스트 네트워크 Ganache를 사용하면 Remix의 Javascript VM 환경처럼 일정 금액이 이더가 들어있는 여러 개의 이더리움 계정이 만들어 집니다. 따라서 로컬 네트워크를 별도로 세팅하지 않아도 되니 테스트에 매우 적합합니다. 또한 계정들은 개인키 설정할 필요가 없는 unlock 상태로 제공됩니다.
개념적으로 어려울 수도 있으니, 그냥 그런가 보다 하고 넘어가셔도 좋습니다.
2. 테스트 파일
위와 같은 구조에서 test 디텍터리에 다음과 같이 테스트 파일을 생성합니다.
- 파일이름: dreamstory.test.js
- 파일위치: test
3. Mocha 테스트 Framework
Mocha라는 테스트 framework이 있습니다. 여기서는 스마트 컨트랙트의 기능들을 테스트 하기 위해서 Mocha를 사용합니다. Mocha에서 주로 사용하는 함수와 목적은 아래와 같습니다. 저도 Mocha를 처음사용하지만, 개념적으로 보면 구글 C++ unnitest의 Test, Fixture와 유사합니다.
Mocha에서는 'it'함수로 개별적 테스트를 진행하고, 이 'it'들을 모아서 그룹핑하는 것이 'describe'입니다. 그리고 테스트를 수행하기 전에 사전에 수행되어야 하는 코드를 'beforeEach'라는 함수에서 처리하도록 합니다.
Mocha를 이용하려면 package.json 파일에서 아래와 같이 "test" 부분을 "Mocha"로 바꿔 주어야 합니다.
4. 테스트 스크립트
그럼 간단히 테스트를 실행할 수 있는 스크립트를 작성해 보겠습니다. 테스트에 사용되는 주요 내용은 다음과 같습니다.
- Ganashe가 생성한 계정 얻어오기
- 컨트랙트 인스턴스 생성하기
- deploy할 컨트랙트 객체 만들기
- deploy하라는 트랜잭션을 전송하기
- 네트워크에 deploy된 컨트랙트 object 내용 출력하기
상세 내용은 코드 주석을 참고해 주세요.
// import assert module to test with assertion
const assert= require( 'assert' );
// local ethereum test network
const ganache= require( 'ganache-cli' );
// web3 constructor function. note that it is Web3 not web3
const Web3= require( 'web3' );
// create web3 instance to connect local test network
const web3= new Web3( ganache.provider() );
// bytecode and api (interface) of compiled contract
const compiled_contract= require( '../ethereum/build/DreamStory.json' );
//// global variables
// ethereum accounts
let accounts;
// deployed contract
let dream_story;
// setup code before running a test
beforeEach( async () => {
// get all the accounts
accounts= await web3.eth.getAccounts();
// create a contract instance with arguments and deploy it
// parse the json interface, so the javascript object can be used for contract
dream_story= await new web3.eth.Contract( JSON.parse( compiled_contract.interface ) )
// tell web3 that we want to deploy a new conpy of the contract.
// do not forget about the arguments that the constructor of the contract requires
// calling deploy does not deploy the contract, it creates an object to be deployed
.deploy({ data: compiled_contract.bytecode, arguments: [100] })
// send transaction that creates the contract
.send( { from: accounts[0], gas: '1000000' } )
});
// test groups
describe( 'DreamStory', () => {
// unit test: simply print out the deployed contract object
it( 'Deploy a DreamStory contract', () => {
// print out deployed contract object
console.log( dream_story );
});
});
이렇게 작성한 후 아래와 같이 테스트를 실행합니다.
$ npm run test
그러면 아래와 같이 테스트가 패스하고, deploy된 컨트랙트 객체의 내용들이 출력됩니다. 테스트를 실행 후 약간의 시간이 걸리는게 느껴지나요? 로컬 네트워크이라고 해도 컨트랙트 객체를 deploy하라는 트랜잭션을 전송하는데 시간이 소요됩니다. 메인 네트워크라면 더 많은 시간이 걸릴 수 있겠죠.
지금까지 해본것을 정리하면 다음과 같습니다.
- 스마트 컨트랙트 소스 파일을 자동으로 컴파일할 수 있는 스크립트
- 로컬 테스트 네트워크에 접속하는 방법
- 컨트랙트 객체를 생성하고 배포하는 테스트 스크립트
다음부터 본격적으로 스마트 컨트랙트의 기능을 테스트해보겠습니다.
오늘의 실습: 작성한 스마트 컨트랙트에서 어떤 부분을 중점적으로 테스트 해야 할까요?