nodejs: Mocha

이 글은 'Testing with Mocha'을 다시 쓴 것이다. Alex Young님이 만든 예제도 내가 쓰기 편하게 좀 고쳤다.

mocha-test

Testing with Mocha를 읽고 Mocha 메뉴얼을 읽으니 훨씬 눈에 잘 들어온다.

Mocha

Mocha는 TJ Holowaychuk이 만든 BDD 프레임워크이다.

이 글에서는 그냥 Mocha로 BDD하는 법에 대해서만 설명한다. 원래 글은 TDD를 하는 법에 대해서 다뤘지만, 다시 정리하면서 BDD로 수정했다. 다른 방법, 다른 프레임워크와의 비교는 @outsider님이 잘 정리해 주었다. @outsider님 블로그 글을 읽어보자.

package.json

먼저 package.json 파일을 다음과 같이 만든다.

{
    "name": "async-testing-tutorial"
    , "version": "0.0.1"
    , "description": "A tutorial for Mocha"
    , "keywords": ["test", "tutorial"]
    , "author": "Alex R. Young <info@dailyjs.com>"
    , "main": "index"
    , "engines": { "node": ">= 0.4.x < 0.7.0" }
    , "scripts": {
        "test": "make test"
    }
    , "devDependencies": {
        "mocha": "1.0.x"
        , "should": "0.6.x"
    }
}

원래 코드는 assert 모듈과 TDD 스타일로 돼 있었지만, 나는 should와 BDD가 더 직관적이라고 생각하므로 바꿨다.

이를 위해 의존성을 추가한다.:

, "devDependencies": {
    "mocha": "1.0.x"
    , "should": "0.6.x"
}

이제 npm install하면 해당 모듈이 설치된다.

Makefile

다음과 같이 Makefile을 만들고 make test를 실행하면 된다.

test:
    @./node_modules/.bin/mocha --require should

.PHONY: test
  • --require should - 테스트 코드에서 should 모듈을 끼워 넣어 준다. 생략하면 테스트 코드에 require('should')를 직접 넣어 줘야 한다.
  • --reporter dot - 테스트 결과를 어떻게 보여줄지 reporter를 고를 수 있다. 생략 시 기본 값은 dot.
  • --u bdd - 테스트 스타일을 고를 수 있는데. 생략 시 기본 값은 bdd.

자세한 옵션은 Mocha 페이지에서 확인한다.

prime Module

Alex Young님이 작성한 모듈 코드:

function nextPrime(n) {
    var smaller;
    n = Math.floor(n);

    if (n >= 2) {
        smaller = 1;
        while (smaller * smaller <= n) {
            n++;
            smaller = 2;
            while ((n % smaller > 0) && (smaller * smaller <= n)) {
                smaller++;
            }
        }
        return n;
    } else {
        return 2;
    }
}

function asyncPrime(n, fn) {
    setTimeout(function() {
        fn( nextPrime(n) );
    }, 10);
}

module.exports.nextPrime = nextPrime;
module.exports.asyncPrime = asyncPrime;

Test

BDD 스타일로 작성한 테스트는 다음과 같다.

var nextPrime = require('./../index').nextPrime
var asyncPrime = require('./../index').asyncPrime;

describe('prime', function() {

    describe('nextPrime', function() {

        it('nextPrime should return the next prime number', function() {
            nextPrime(7).should.equal(11);
        });

        it('zero and one are not prime numbers', function() {
            nextPrime(0).should.equal(2);
            nextPrime(1).should.equal(2);
        });
    });

    describe('asyncPrime', function() {

        it('asyncPrime should return the next prime number', function(done) {
            asyncPrime(128, function(n) {
                n.should.equal(131);
                done();
            });
        });
    });
});

비동기 테스트 예제인 'asyncPrime'은 done()을 호출해서 테스트가 성공했음을 알린다.

hooks

  • before() - describe()를 시작하기 전에 한번
  • after() - describe()를 끝내고 나서 한번
  • beforeEach() - describe() 안에 있는 it()이 시작할 때마다 한번
  • afterEach() - describe() 안에 있는 it() 이 끝날 때마다 한번

위 테스트코드에 hooks을 추가해보고:

var nextPrime = require('./../index').nextPrime
var asyncPrime = require('./../index').asyncPrime;

describe('prime', function() {

    before(function(){
        console.log('before');
    });

    after(function(){
        console.log('after');
    });

    beforeEach(function(){
        console.log('beforeEach');
    });

    afterEach(function(){
        console.log('afterEach');
    });

    describe('nextPrime', function() {
        before(function(){
            console.log('new before');
        });

        it('nextPrime should return the next prime number', function() {
            nextPrime(7).should.equal(11);
        });

        it('zero and one are not prime numbers', function() {
            nextPrime(0).should.equal(2);
            nextPrime(1).should.equal(2);
        });
    });

    describe('asyncPrime', function() {
        afterEach(function(){
            console.log('new afterEach');
        });

        it('asyncPrime should return the next prime number', function(done) {
            asyncPrime(128, function(n) {
                n.should.equal(131);
                done();
            });
        });
    });
});

결과는 다음과 같다:

  before
new before
beforeEach
.afterEach
beforeEach
.afterEach
beforeEach
.new afterEach
afterEach
after


  ✔ 3 tests complete (25ms)

'new afterEach'나 'new before' 부분에서 스택처럼 동작해서 상위 describe()에 정의한 것은 생략될까 싶었는데, 아니었다. 모두 호출된다.

Alex Young 만든 코드는 async-testing-tutorial이고 내가 수정한 코드는 async-testing-tutorial-pismute이다.

TDD 스타일로 테스트를 작성하고 assert방식의 expecting을 선호한다면 Alex Young만든 코드를 보는 것이 낫다.