mocha를 이용한 node.js

TDD(Test Driven Development)로 얻을 수 있는 장점은 많다. 궁극적으로는 테스팅하는데 시간을 낭비하지 않고 훌륭한 코드 품질을 얻고자 함이겠다. 개인적으로는 TDD를 오래전부터 들어왔어도, Testcase를 먼저 만들고 실제 함수 구현을 나중에 하는 구조가 익숙하지 않아 거부하였지만,(또한 회사에서 TDD를 산출물로 관리하려는 듯 하여 거부하기도 했지만..) 이번에 node.js로 개인 프로젝트를 하면서 문득 TDD를 적용해 보고 싶어서 진행했는데, 의외로 아무런 거부감 없이 TDD의 강력함을 절실히 느끼고 있다.

우선 프로젝트가 BBB에 node.js를 올리는 것이기 때문에 node.js용 TDD framework를 찾아보았다. mocha라는 프로젝트가 유명했고,이 블로그(링크)가 매우 잘 정리되어 있었다. 해당 블로그를 바탕으로 node.js용 TDD를 설명해 볼까 한다.

mocha를 우선 깔아야 한다. 설치는 매우 간단하다. 단 -g 옵션으로 글로벌로 설치한다.


$ sudo npm install -g mocha

다음은 SuperAgent와 expect.js를 설치하라고 되어 있는데, SuperAgent는 ajax를 보다 보기 좋게 개량한 api이고, expect.js는 TDD의 assertion library이다. 나의 경우 TDD를 하고자 하는 모듈이 ajax가 필요 없었으므로 SuperAgent는 설치 하지 않았다. 대신 expect.js를 설치했는데, mocha는 다양한 assertion library를 사용할 수 있게 처리해 두었다. 살펴보니 expect.js 문법이 가장 직관적이였다.


$ npm install expect.js

자 이제 재료는 다 준비되었다. 절대 구현을 먼저 하지 말고, 테스트 케이스부터 정하자. 난 개발자니까 테스트 케이스를 만드는 것은 재미 없다고 생각할 수 있다. 그럼 SRS로부터 해당 모듈이 해야 한다고 생각하는 기능 정의를 해보자. “테스트 케이스”를 만드는게 아니고 “기능 정의”라고 하면 보다 개발자스러운 접근이다. 거부감도 적다. 🙂

구현하고자 하는 모듈이 사람이름을 추가하고 삭제하는 모듈이라고 하자.

“이 모듈은 초기화를 할 수 있다.”

“이 모듈은 사람이름을 등록할 수 있다.”

“이 모듈은 사람이름을 삭제할 수 있다.”

“이 모듈은 저장된 사람이름의 개수를 알려줄 수 있다.”

“이 모듈은 최대로 저장할 수 있는 사람이름의 개수는 10개이다.”

“이 모듈은 같은 이름을 가진 사람이 있을 경우 등록 할 수 없다.”

대충 생각해 보니 이 정도인 듯 하다. 그럼 이 기능 정의를 바탕으로 testcase를 짜보자.

</pre>
var expect = require('expect.js');
describe('[name]', function() {
it('The module shell be initialized.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The module shell register a name.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The module shell remove a name.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The module shell provide the number of registered name.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The maximum number of registered name shall be 10.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The module do not accept the name, if the name is already registered.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
});
<pre>

먼저 require()로 expect.js 모듈을 로드한다. 이후 describe()함수로 test group를 정의한다. 각각의 testcase는 it()함수로 정의한다. it()함수의 콜백함수는 done이라는 객체를 인자로 받게 되어있다. javascript의 asynchorous한 특성을 done 객체로 손 쉽게 해결했다. mocha가 done객체를 넘겨주면 필요한 기능 검증을 하고(synch든, asynch든) 다 해결되면 done()불러주면 다음 testcase로 넘어가게 된다.

실행을 해보면 6개의 testcase는 모두 성공이다.


$ mocha name_test.js

․․․․․․

6 passing (8 ms)

$

이제 첫번째 testcase를 수정해 보자.


it('The module shell be initialized.', function(done) {
var name = require('./name.js');
var ret = true;
expect(ret).to.equal(true);
done();
 });

require()로 name.js를 불렀다. 바로 mocha로 테스트 해보자.


$ mocha name_test.js

․․․․․․

5 passing (9 ms)
 1 failing

1) [name] The module shell be initialized.:
 Error: Cannot find module './name.js'
 at Function.Module._resolveFilename (module.js:338:15)
 at Function.Module._load (module.js:280:25)
 at Module.require (module.js:364:17)
 at require (module.js:380:17)
 at Context.<anonymous> (/............................/tdd/name_test.js:5:14)
 at Test.Runnable.run (/usr/local/lib/node_modules/mocha/lib/runnable.js:194:15)
 at Runner.runTest (/usr/local/lib/node_modules/mocha/lib/runner.js:355:10)
 at /usr/local/lib/node_modules/mocha/lib/runner.js:401:12
 at next (/usr/local/lib/node_modules/mocha/lib/runner.js:281:14)
 at /usr/local/lib/node_modules/mocha/lib/runner.js:290:7
 at next (/usr/local/lib/node_modules/mocha/lib/runner.js:234:23)
 at Object._onImmediate (/usr/local/lib/node_modules/mocha/lib/runner.js:258:5)
 at processImmediate [as _immediateCallback] (timers.js:330:15)

에러를 살펴보면 name.js가 없다. 그럼 이 testcase의 에러를 없애기 위해 name.js를 만든다.


module.exports = {

};

에러가 없다. 그럼 이제 테스트케이스 제목처럼 초기화가 되어야 함으로 무작정 init()함수를 불러보자.


it('The module shell be initialized.', function(done) {
 var name = require('./name.js');
 name.init();
 var ret = true;
 expect(ret).to.equal(true);
 done();
 });

mocha로 돌려보자. 에러가 있을까?


$ mocha name_test.js

․․․․․․

5 passing (9 ms)
 1 failing

1) [name] The module shell be initialized.:
 TypeError: Object #<Object> has no method 'init'
 at Context.<anonymous> (/..................../tdd/name_test.js:6:8)
 at Test.Runnable.run (/usr/local/lib/node_modules/mocha/lib/runnable.js:194:15)
 at Runner.runTest (/usr/local/lib/node_modules/mocha/lib/runner.js:355:10)
 at /usr/local/lib/node_modules/mocha/lib/runner.js:401:12
 at next (/usr/local/lib/node_modules/mocha/lib/runner.js:281:14)
 at /usr/local/lib/node_modules/mocha/lib/runner.js:290:7
 at next (/usr/local/lib/node_modules/mocha/lib/runner.js:234:23)
 at Object._onImmediate (/usr/local/lib/node_modules/mocha/lib/runner.js:258:5)
 at processImmediate [as _immediateCallback] (timers.js:330:15)

예상하지만 init()함수가 없다고 나온다. 그럼 name.js에서 init() 함수를 만들어 주자.


module.exports = {
init:function() {
}
};

mocha를 돌려본다. 에러가 있을까?


$ mocha name_test.js

․․․․․․

6 passing (9 ms)

에러가 없다. 이 와중에 name.js에 init()함수가 만들어 졌다. 이렇게 반복하면서 TDD 구현과 실제 모듈 구현을 함께 발전해 나갈 수 있다. 끝까지 다 구현해서 name.js 모듈을 완성하고 싶지만 글이 너무 길어져서 여기서 마무리 하겠다. 소개용이 아닌 개인 프로젝트용 모듈은 TDD를 통해 안정적인 소프트웨어를 구현할 수 있었다.

혹자는 TDD 구현 코드와 실제 모듈 구현 코드가 이중으로 부담이 되기 때문에 거부감이 있을 것이다. 초반에는 힘들 수 있다. 그러나 TDD 개발이 몸에 익으면 TDD 구현 코드는 copy & paste가 주로 이루어 진다. 그 정도의 노력으로 향후 실제 모듈의 안정성을 보장받는다면 안 할 이유가 없다고 생각된다. 단 관리 목적의 TDD라면 다시한번 생각해 볼 필요는 있겠다. 🙂

 

Advertisements

About musart
hello

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: