HEROPY
Tech
{{ scrollPercentage }}%

Node.js 테스트 프레임워크 Mocha

nodejs mocha

단순한 자바스크립트 개발에서는 단위 테스트가 필요하지 않았습니다.
오히려 테스트를 기반으로 개발한다는 개념 자체가 개발을 더욱 어렵게 했었죠. 뭐, 저는 그랬습니다.
하지만 자바스크립트 개발이 점차 복잡해지면서 작성한 함수가 잘 동작하고 제대로된 값을 반환하는지 검증하는 과정은 간단한 기능에서조차 필수가 되어버린 듯합니다.

그리하여 살펴볼 Mocha(모카)는 테스트 러너를 지원하는 테스트 프레임워크 입니다.
자체 Assertion(어써션)은 지원하지 않으며, 필요한 Assertion 라이브러리를 가져와 사용할 수 있습니다.

Assertion은 에러가 없는 프로그램을 작성하기 위한 하나의 수법을 의미합니다.

Node.js에서 지원하는 Assert 모듈을 사용할 수도 있으며, ChaiShould.js 같은 BDD, TDD 방식의 다양한 Assertion 라이브러리가 있습니다.
Mocha는 각 모듈에서 별도로 호출하지 않으며, 사용할 Assertion 라이브러리만 require()로 호출합니다.

설치

전역으로 설치하면 mocha 명령어로 실행할 수 있습니다.

$ npm install mocha -g

개발 의존성 모듈로 설치할 경우 package.jsonscripts 항목에 명령을 설정합니다.

$ npm install mocha --save-dev

package.json:

{
  "scripts": {
    "test": "mocha"
  }
}

테스트 예제

Mocha에 대해서 살펴보기 전에, 아주 간단하게 테스트용 코드를 작성합니다.

app.js:

module.exports = {
  sayHello: function () {
    return 'hello';
  }
}

test/디렉토리에 별도의 파일(app.spec.js)을 생성한 후 테스트할 코드를 require()로 가져와 테스트를 진행합니다.

test/app.spec.js:

const sayHello = require('../app').sayHello;

if (sayHello) {
  console.log('sayHello should return "hello"');
  if (sayHello() === 'hello') {
    console.log('Success');
  } else {
    console.log('Fail');
  }
}

예전에는 위와 같이 Alert이나 Console로 작동 여부를 확인했지만, 우리는 Mocha를 활용합니다.
내용을 다음과 같이 수정하여 단위 테스트를 진행합니다.

describe()는 테스트의 범위를 설정하고, it()는 단위 테스트를 설정합니다.
인수로 사용한 done은 비동기 단위 테스트를 완료할 때 유용합니다.

test/app.spec.js:

const sayHello = require('../app').sayHello;

describe('App test!', function () {
  it('sayHello should return hello', function (done) {
    if (sayHello() === 'hello') {
      done();
    }
  });
});

mocha <filename>으로 실행할 경우 해당 파일의 테스트를 진행합니다.
만약 mocha 명령과 함께 실행 파일(filename)을 지정하지 않으면, 현재 경로의 test/ 디렉토리에 있는 모든 .js파일을 실행합니다.
우리는 test/ 디렉토리 안에 app.spec.js를 생성하였으므로 다음과 같이 실행할 수 있습니다.

$ mocha
# or
$ npm test
# or
$ mocha test/app.spec.js
App test!
  ✓ sayHello should return hello

1 passing (5ms)

Node.js에서 제공하는 assert 모듈을 활용하면 좀 더 편리하게 테스트할 수 있습니다.

const assert = require('assert');  // Node.js `assert` module
const sayHello = require('../app').sayHello;

describe('App test!', function () {
  it('sayHello should return "hello"', function () {
    assert.equal(sayHello(), 'hello');
  });
});

비동기 테스트

Mocha를 이용하면 간단하게 비동기 테스트를 진행할 수 있습니다.
it()의 콜백 인수로 done를 사용하면 자동으로 비동기 테스트를 인식하고, 비동기 로직이 완료 후 done()을 실행하면 테스트가 완료됩니다.

const fs = require('fs');

describe('App test1', function () {
  it('async test', function (done) {
    fs.readFile(__filename, done);
  });
});

비동기 테스트는 제한 시간(timeout)으로 2초가 경과하면 자동으로 테스트 실패가 됩니다.
테스트를 진행할 때 -t, --timeout 옵션을 이용하거나, 단일 테스트 내 this.timeout()을 이용하여 제한 시간을 설정할 수 있습니다.

# 전체 테스트의 제한 시간 설정
$ mocha -t 3000

혹은

const fs = require('fs');

describe('App test1', function () {
  it('async test', function (done) {
    this.timeout(3000);  // 단일 테스트의 제한 시간 설정
    fs.readFile(__filename, done);
  });
});

중첩

describe() 안에 또 다른 describe()를 중첩하여 사용할 수 있습니다.

app.js:

module.exports = {
  sayHello: function () {
    return 'hello';
  },
  addNumbers: function (a, b) {
    return a + b;
  }
};

Assetion 라이브러리로 Chai의 Should 스타일을 사용했습니다.

test/app.spec.js:

const should = require('chai').should();
const app = require('../app');

describe('# App test', function () {
  describe('# sayHello', function () {
    it('should return hello', function () {
      app.sayHello().should.equal('hello');
    });
    it('should a string type', function () {
      app.sayHello().should.be.a('string');
    });
  });

  describe('# addNumbers', function () {
    it('should greater than 5', function () {
      app.addNumbers(3, 4).should.be.above(5);
    });
  });
});
$ npm test
# App test
    # sayHello
      ✓ should return hello
      ✓ should a string type
    # addNumbers
      ✓ should greater than 5

3 passing (9ms)

Hooks

Mocha에서는 before(), after(), beforeEach(), afterEach()의 4가지 함수(hook)를 제공하여 테스트 코드 전후를 제어할 수 있습니다.

describe('hooks', function() {

  before(function() {
    // 블록 범위 내 모든 테스트 전에 실행
  });

  after(function() {
    // 블록 범위 내 모든 테스트 후에 실행
  });

  beforeEach(function() {
    // 블록 범위 내 각 테스트 직전에 실행
  });

  afterEach(function() {
    // 블록 범위 내 각 테스트 직후에 실행
  });

  // test cases
});
const assert = require('assert');
const app = require('../app');

describe('App test!', function () {
  before(function () {
    console.log('before hook');
  });
  after(function () {
    console.log('after hook');
  });
  beforeEach(function () {
    console.log('beforeEach hook');
  });
  afterEach(function () {
    console.log('afterEach hook');
  });

  it('A test', function () {
    assert.equal(app.a(), 'A!');
  });
  it('B test', function () {
    assert.equal(app.b(), 'B!');
  });
});
$ npm test
  App test!
before hook
beforeEach hook
    ✓ A test
afterEach hook
beforeEach hook
    ✓ B test
afterEach hook
after hook

    2 passing (11ms)

Options

mocha 명령어로 테스트를 진행할 때 다음과 같은 옵션을 사용할 수 있습니다.

$ mocha [debug] [options] [files]

-V, --version: Mocha의 버전을 확인합니다.
-A, --async-only: 비동기 테스트만 허용합니다.
-c, --colors: 코드 색상을 활성화합니다.
-C, --no-colors: 코드 색상을 비활성화합니다.
-R, --reporter <name>: 테스트 결과에 대한 리포팅 방식을 설정합니다.
-b, --bail: 첫번째 실패한 테스트만 확인합니다.
-g, --grep <pattern>: 지정한 정규표현식 패턴과 일치하는 테스트만 진행합니다.
-f, --fgrep <string>: 지정한 문자열을 포함하는 테스트만 진행합니다.
-i, --invert: --grep, --fgrep 결과와 일치하지 않는 테스트만 진행합니다. ex> mocha -f 'abc' -i
-r, --require <name>: 가져올 모듈(should.js, chai 같은 Assertion 라이브러리)을 지정합니다.
-t, --timeout <ms>: 지정한 테스트가 제한 시간(기본값 2000)을 지나면 실패합니다. 단일 테스트의 제한 시간을 설정할 수 있습니다.
-u, --ui <name>: 사용자 인터페이스를 지정합니다. ex> mocha -u tdd
-w, --watch: 테스트 대상 파일들을 감시합니다.

기타 옵션들