Test Framework - Jest
이번 내용은 리액트 테스트 프레임워크인 jest에 대해 설명하려고 합니다.
필자는 우선 테스트라는 것은 전혀 생각도 하지 않았고, 몇년전 모임 스터디중 Jasmine(자스민)을
아주 잠깐 맛보기로 써봤을 뿐이였습니다. 그당시 자스민을 쓰면서도 이걸 언제 작성하고 할까.. 귀찮은데..
재미도없어.. 이런 마음이 컷기 때문에 맛보기 정도만 해보고 그대로 저의 기억속에서 잊혀졌습니다.
그러다 최근 팀에 새로 입사하신분께서 jest에 대해 설명해주셨고, 유용함을 이제서야 느끼게 되어
이 글을 작성하게 되었습니다.
그렇다면 왜 테스트를 해야하는가에 대해 먼저 얘기해 보겠습니다.
테스트.. Why?
물론 저는 아직도 테스트를 반드시, 무조건 해야한다고 생각하지는 않습니다.
실무에 이미 도입되어있거나 혹은 도입하기위해 실무에 바로 써야하는 분들의 경우도 있을 것 이며,
아직 테스트코드에 대해 모르거나 예전의 저처럼 비관적으로 바라보는 경우도 있을 것 입니다.
왜냐하면 혼자하는 작업이거나 소규모 프로젝트라면 오히려 진행 속도를 늦출 수 있기 때문입니다.
일단 장점과 단점을 설명하자면 아래와 같습니다.
장점
- 리팩토링할때 좋다.
- 리팩토링 후에도 해당 모듈이 의도대로 작동하고 있음을 유닛 테스트를 통해서 확신할 수 있는데, 이를 회귀 테스트(Regression testing)라 합니다.
- 협업시 좋다.
- A의 코드와 B의 코드로 C 라는 기능을 구현 하는데, A를 수정한뒤에 A 코드의 정상 동작을 확인 하였는데, 갑자기 A에서 문제가 생겼다고 했을시 기존에 유닛 테스트를 했다면
A코드의 문제를 바로 발견하고 수정했을것입니다. 하지만 유닛코드를 작성하지 않았다면 어디서 버그가 나타났는지 찾기 힘들것입니다.
- A의 코드와 B의 코드로 C 라는 기능을 구현 하는데, A를 수정한뒤에 A 코드의 정상 동작을 확인 하였는데, 갑자기 A에서 문제가 생겼다고 했을시 기존에 유닛 테스트를 했다면
- 그룹내에 공용으로 사용하여 테스트 코드를 늘릴 수 있다. ( 예외적인 상황 )
- 예를 들면 같은 개발팀 내에서 validation 관련 컴포넌트 & 테스트 코드를 공유하며 하나하나씩 테스트 코드를 작성하다보면 테스트 코드가 공유되고 양도 늘어나 더욱 빠른 테스트 환경을 만들 수 있다.
단점
- 시간이 걸린다.
- 테스트 코드 작성 및 러닝커브
이처럼 유닛테스트는 개발 시간을 증가 시키는것 처럼 보일 수 있습니다.
하지만 유닛테스트를 함으로써 개발 기간 중 대부분을 차지하는 디버깅 시간을 단축시킬수 있기 때문에 필수는 아니지만
할 수 있다면 작성 하며 개발 하는 것을 개인적으로는 추천합니다.
*필자는 그전에 개발을 하였을 때 테스트 없이 개발 하였고, 리팩토링하거나 코드 수정시 기존의 기능들이 제대로 동작하는지 하나하나 확인 했었는데,
테스트 코드를 작성함으로써 테스트만 돌리면 따로 확인작업을 하지않아도 된다는 것이 매력적으로 다가왔습니다.
물론 테스트 코드가 100%를 테스트 해주진 않겠지만 ( 테스트 코드 작성에 따라 다름으로 ) 어느정도 커버해준다는 것만 으로도 큰 매력이 있다고 생각합니다.
다음으로 이제 jest에 대해 설명하도록 하겠습니다.
(필자는 react를 사용하며 react에 최적화되어있는 test framework인 jest에 대해 알아보려고 합니다.)
Jest란?
Jest는 페이스북에서 만든 테스트 프레임워크입니다.
React를 포함한 모든 자바스크립트 코드를 테스트하는데 사용될 수 있습니다.
Jest의 철학중 “Zero-Configuration”이 있는데, 이는 아래와 같이 설정 없는 테스트 환경을 제공한다고 합니다.1
2npm install --save-dev jset // jest 설치
npm run jest // jest 시작
이렇게 간단하게 설치와 테스트를 할 수 있습니다.
물론 테스트 코드를 작성하는것은 별개의 작업이지만 따로 설정이 필요없이 설치만 하면 테스트를 할수 있는 환경이 이루어집니다.
이렇게 Jest를 사용하여 저희는 Unit Test(단위 테스트)를 해보려고 합니다.
Unit Test(단위 테스트)란?
Unit Test란, 소프트웨어를 최소한의 단위로 쪼개서 아주 작은 단위로 테스트 하는 것 을 말합니다.
예를 들자면 React에 A라는 Counter 컴포넌트가 있다면, A컴포넌트의 카운트 증가 함수를 테스트 하거나 카운트 감소 함수를 테스트 하는 것을 말합니다.
사용법
기본적으로 React는 사용 하실줄 안다는 것을 가정하여 설명하도록 하겠습니다.
( 아래와 같이 Counter.jsx 컴포넌트 코드와 Counter.test.jsx 컴포넌트 테스트 코드를 먼저 작성합니다. )
Counter.jsx1
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
32import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
value: 1
}
}
onIncrease = () => {
this.setState(({value}) => ({ value: value + 1 }));
}
onDecrease = () => {
this.setState(({value}) => ({ value: value <= 0 ? value : value - 1 }));
}
render() {
const { value } = this.state;
return (
<div>
<h1>카운터 예제</h1>
<h2>{value}</h2>
<button onClick={this.onIncrease}>+</button>
<button onClick={this.onDecrease}>-</button>
</div>
);
}
}
export default Counter;
Counter.test.jsx1
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
32import React from 'react';
import renderer from 'react-test-renderer';
import Counter from './index';
describe('Counter', () => {
let component = null;
it('renders correctly', () => {
component = renderer.create(<Counter />);
});
it('matches snapshot', () => {
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
//increase 동작 확인
it('increases correctly', () => {
component.getInstance().onIncrease();
expect(component.getInstance().state.value).toBe(2); // value 값이 2인지 확인
const tree = component.toJSON(); // re-render
expect(tree).toMatchSnapshot(); // 스냅샷을 비교합니다.
});
// decrease 동작 확인
it('decreases correctly', () => {
component.getInstance().onDecrease();
expect(component.getInstance().state.value).toBe(1); // value 값이 1인지 확인
const tree = component.toJSON(); // re-render
expect(tree).toMatchSnapshot(); // 스냅샷을 비교합니다.
});
});
중요한 것은 Counter.test.jsx 입니다. Counter.test.jsx 코드를 보시면
renderer, describe, it, expect등 처음 보는 메서드 들이 있을 것입니다.
describe와 it은 jest에서 제공되는 함수입니다.
describe는 테스트 묶음을 정의하고, it은 테스트 케이스를 정의 합니다.
( Counter.test.jsx 테스트 묶음(Suite)은 4개의 테스트를 가지고 있습니다. )
expect도 jest에서 제공하는 함수로 테스트값과 예상값이 일치하는지 여부를 판단합니다.
일치여부 뿐만 아니라 다른 비교도 할 수 있는데 Jest - Expect에서 확인할 수 있습니다.
그리고 describe 와 it 에서 첫번째 파라미터는 해당 작업의 설명을 넣어주게 되는데,
describe 에서는 어떤 기능을 확인하는지, 그리고 it 부분에선 무엇을 검사해야 되는지에 대한 설명을 넣으시면 됩니다.
renderer는 스냅샷 테스트를 위해 사용되는 함수입니다. renderer를 사용하기위해서는 먼저 react-test-renderer 를 설치해 주어야합니다.1
npm install --save-dev react-test-renderer
react-test-renderer의 create() 함수는 인자로 리액트 컴포넌트를 받아 렌더링합니다.
이후 toMatchSnapshot() matcher를 통해 렌더링된 컴포넌트를 스냅샷과 비교할 수 있습니다.
SnapShot(스냅샷) 테스트란?
결과값을 파일로 저장해서 테스트할 때마다 테스트 결과값을 파일의 결과값과 비교합니다.
비교해서 다를때 개발자가 수정을 한 코드이면 업데이트를 하면되고( npm run jest – -u ),
의도하지 않은 코드라면 버그라고 판단하고 코드를 수정하면 될 것 입니다.
위의 Counter.test.jsx 테스트를 실행하면
이렇게 해당 테스트 코드 위치에 snapshots 폴더가 생기고 스냅샷 결과값은 아래와 같이 나오게된다.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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62exports[`Counter decreases correctly 1`] = `
<div>
<h1>
카운터
</h1>
<h2>
1
</h2>
<button
onClick={[Function]}
>
+
</button>
<button
onClick={[Function]}
>
-
</button>
</div>
`;
exports[`Counter increases correctly 1`] = `
<div>
<h1>
카운터
</h1>
<h2>
2
</h2>
<button
onClick={[Function]}
>
+
</button>
<button
onClick={[Function]}
>
-
</button>
</div>
`;
exports[`Counter matches snapshot 1`] = `
<div>
<h1>
카운터
</h1>
<h2>
1
</h2>
<button
onClick={[Function]}
>
+
</button>
<button
onClick={[Function]}
>
-
</button>
</div>
`;
이렇게 작성된 스냅샷과 현재 코드와 비교하여 달라진 곳이 있는지 테스트 하게 되는 것 입니다.
지금까지 설명한데로 진행하게되면, 터미널에는 아래와 같은 메세지가 출력될 것입니다.
이를 바탕으로 더 많은 테스트 코드를 작성하여 안정성있는 코드를 작성하실 수 있길 바랍니다.