단위 테스트 삽질기

Unit Test 적용하게 된 계기

최근 실무에서 진행 중인 프로젝트에 처음 Vuejs 도입에 참 우여곡절이 많았다. 아무래도 Front 쪽에 하나의 프레임워크를 도입하는 부분은 모든 이들의 공감을 이끌어 내야 해서 쉽지 않았던 것도 있었다. 아마도 처음 Javascript 프레임 워크를 도입하는 것이 팀원들에게는 부담이 있어서 이지 않을까 싶었다. 그 부담의 원인 중 하나는 장애에 대한 대응도 있었다. 팀원 중에 Javascript 프레임 워크를 명확하게 아는 사람이 많지 않았는데 Javascript 프레임워크를 도입하게 되면 Backend에 몰리고 있는 장애 대응의 포인트가 Backend보다는 Frontend 쪽으로 분산되긴 하지만 Frontend의 소수 인원으로 짊어질 수 있느냐가 관건이었다.

물론 Unit Test가 이러한 환경에서의 만능은 아니다. 분명 Unit Test 코드를 작성하면서도 휴먼 에러는 발생할 수 있고, 이에 따라 테스트 하지 못한 모듈에서는 장애가 발생할 수도 있다. 하지만 Unit Test 코드를 작성하면서 놓치고 있는 부분을 잡아낼 수도 있을 것이고, 무엇보다 예측 가능한 코드를 작성하여 최대한 coverage를 높여 예측 가능한 장애는 되도록 줄이자가 목표였다.

Unit Test란 무엇인가?

아래는 Wiki 기재되어 있는 Unit test에 대한 설명이다.

유닛 테스트는 컴퓨터 프로그래밍에서 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차 이며, 모든 함수와 테스트 케이스를 작성하는 절차를 말한다. 이를 통해서 언제라도 코드 변경으로 인한 문제가 발생할 경우, 단시간 내에 이를 파악하고 바로 잡을 수 있도록 해준다. 이상적으로 테스트 케이스는 서로 분리되어야 한다. 이를 위해 Mock object를 생성하는 것도 좋은 방벙이다. 유닛테스트는 ( 일반적인 테스트와 달리 ) 개발자 뿐만 아니라 더 심도있는 테스트를 위해 테스터에 의해 수행되기도 한다.

유닛테스트의 목적은 위와 같이 디버깅 시간을 단축시킬 수 있으며, 코드에 변화가 생겼을 때 그 변화 감지를 빠르게 감지할 수 있다. 그럼으로써 생길 수 있는 장애 대응에 신속하게 대처할 수 있다. 그리고 무엇보다 유닛테스트 도입이 시급하다고 생각했던 것은 위와 같은 장애 대응과 같이 이유도 있지만, 리펙토링에 대한 이유도 있었다. 유닛 테스트를 진행하면 위에 설명한 것 처럼 코드에 변화가 생겼을 때 그 변화 감지를 빠르게 감지할 수 있다. 그럼 개발자로서는 리펙토링을 할 때, ‘혹시나 내가 만든 코드가 또 다른 사이드 이펙트를 가져오진 않을까’ 하는 소위 이야기 하는 ‘사리는 코딩’으로부터 자유로울 수 있다고 생각했다. 이러한 용어를 회귀 테스트 라 한다고 한다. 회귀 테스트란 회귀 버그, 이전에 제대로 작동하던 기능에 문제가 생긴 경우 그 버그를 찾는 테스트 방식이다.

물론 위에 처엄 모든 함수와 테스트 케이스를 작성하는 것은 쉽지 않다. 말 그대로 전문적인 테스터가 있으면 모를까, 대부분의 경우 그렇지 않을 것이고 유닛테스트는 개발자의 몫이다. 그리고 비니지스 로직만 구현하다가 테스트 코드를 작성한다고 하면 관리자들은 대부분 불필요한 작업을 한다고 여기는 것 같다. 물리적인 시간 때문이라도, 모든 함수에 다 유닛 테스트를 붙이는 것이 아니라, 중요 로직 혹은 장애의 리스크가 큰 함수 단위로 유닛 테스트를 붙이는 것이 효율적인 방법이라고 생각한다.

Unit Test 적용하기

처음 유닛테스트를 찾아봤을 떄 대부분의 글들에서 간단한 유닛테스트에 대한 것만 설명하고 있었다. 그 글들을 보며, 실제 어떻게 적용을 시켜야하는지조차 감을 잡기가 힘들었다. 그래서 나름대로의 개인적으로 테스트를 돌리되, 많은 개발자들이 처음 프레임워크나 언어를 접할 때 많이 작성하는 Todo app에 적용해보기로 했다. 참고로 이 글을 아예 처음 유닛테스트를 접할 경우에는 조금 이해하기 어려울 수 있다. 그런 경우는 아래에 적용된 Github의 repository의 링크를 남겨두니, clone을 이용해 내려받아 실행시켜보면 어느정도 이해를 하는데 도움이 될 수 있다.

일단 적용하게된 프레임워크는 Vuejs에다가 적용을 하며, 유닛 테스트에는 Karma + Mocha + Chai를 이용하여 작성하기로 했다. 해당 예제에 대한 것은 아래의 링크를 통해서 볼 수 있다.

https://github.com/MartinYounghoonKim/vuejs-todo

일단 파일 디렉토리는 아래와 같다.

1
2
3
4
5
6
┌── src [App entry diretory]

└── test [unit test]
└── unit
├── coverage
└── specs

일단 App 의 entry 디렉토리와 unit test의 디렉토리르 물리적으로 분리를 시켰다.

1
$ npm install --save-dev karma karma-coverage karma-mocha karma-phantomjs-launcher karma-phantomjs-shim karma-sinon-chai karma-sourcemap-loader karma-spec-reporter karma-webpack mocha sinon sinon-chai

그 후 필요한 모듈을 npm을 이용하여 설치해준다.

Karma의 config에 대해서 자세히 알고 싶다면 여기

1
2
3
4
5
6
7
8
┌── src [App entry diretory]

└── test [unit test]
└── unit
├── coverage
├── specs
├── index.js
└── karma.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// karma.config.js
var webpackConfig = require('../../build/webpack.test.conf');

module.exports = function (config) {
config.set({
browsers: ['PhantomJS'],
frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
reporters: ['spec', 'coverage'],
files: [
'../../node_modules/babel-polyfill/dist/polyfill.js',
'./index.js'
],
preprocessors: {
'./index.js': ['webpack', 'sourcemap']
},
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true
},
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
}
})
}
// index.js
import Vue from 'vue';

Vue.config.productionTip = false;

const testsContext = require.context('./specs', true, /\.spec$/);
testsContext.keys().forEach(testsContext);

const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/);
srcContext.keys().forEach(srcContext);

위와 같이 설정하면 일단 vuejs 컴포넌트들의 유닛테스트를 하기 위한 어느정도 설정은 되었다.

일단은 가볍게 Vue 컴포넌트가 마운트가 되는지 부터 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require('es6-promise').polyfill();

import { mount } from 'avoriaz';

import Header from '@/components/Header';

describe('Header.vue', () => {
it('Should render correct contents with header class attributes', () => {
const HeaderComponent = mount(Header);
const headerDom = HeaderComponent.find('div')[0];

expect(headerDom.hasClass('header')).to.equal(true);
});
});

위에 코드에서 보면 avoriaz를 사용하고 있다. avoriaz는 Vue 컴포넌트의 유닛테스트를 보다 편하게 하는 utility library이다. avoriaz의 api에 대한 것은 여기를 클릭하면 된다.

위의 코드를 보면 Header 컴포넌트를 마운트 하며, 마운트 된 Header 컴포넌트의 div가 header라는 class를 가지고 있는지에 대해 테스트를 하고 있다. 위와 같이 작성 아마 제대로 마운트 되고 있다 라는 초록색 글씨의 결과를 얻을 수 있다.

/images/unittest/unittest1.png

위와 같이 초록색 글씨가 보여진다면, 첫번째 유닛 테스트는 성공했다. 하지만 실제 유닛 테스트를 적용할 때는 위와 같이 마운트 여부에 대해서 테스트하는 경우는 세세한 것까지 테스트하는 경우는 시간적인 여유 때문에 많지 않을 것이다. 다음 포스트에서는 실제 Todo app이 비지니스 로직을 포함하고 있다는 가정하에의 유닛 테스트를 돌려보겠다.


출처

현재 이커머스회사에서 frontend 개발자로 업무를 진행하고 있는 Martin 입니다. 글을 읽으시고 궁금한 점은 댓글 혹은 메일(hoons0131@gmail.com)로 연락해주시면 빠른 회신 드리도록 하겠습니다. 이 외에도 네트워킹에 대해서는 언제나 환영입니다.:Martin(https://github.com/martinYounghoonKim
단위 테스트 삽질기
초보자의 Docker 사용기