소프트웨어 테스트 (with Jest)
소프트웨어를 개발하고 배포했을때 코드가 원하는대로 동작한고 바라던 결과를 낸다면 좋겠지만 그렇지 않은 경우도 많다. 프로젝트가 복잡해질수록 그러한 상황이 빈번하게 발생할 것이다. 이러한 문제를 해결하기 위해 많은 개발자들이 테스트 코드 작성을 지향하고 있다.
테스트 코드를 작성하는 이유는 무엇일까?
- 작성한 기능이 제대로 동작하는지 확인 가능
- 에러를 사전에 확인하고 수정함으로써 개발시간을 단축
- 테스트 코드자체가 문서로써의 역할도 해줌
하지만 프로젝트가 커질수록 기능끼리의 연관관계도 복잡해지기 때문에 테스트를 작성하기도 어려워진다. 그렇기 때문에 복잡성을 최대한 해소하고 유지/보수를 수월하게 하기 위한 아키텍쳐와 디커플링을 위한 여러가지 패턴들은 테스트 작성에 큰 영향을 줄 수밖에 없다.
테스트의 종류
테스트의 종류는 다양하다. 테스트하고자 하는 범위, 목적, 방법에 따라 나뉘어진다.
테스트를 이야기할때 보통 피라미드 이미지를 예시로 든다. 피라미드는 위처럼 유닛테스트, 통합테스트, UI테스트로 나뉘어진다.(프론트의 경우 UI테스트, 백엔드의 경우 기능테스트라고도 부른다.)
단위테스트(Unit Test)
단위테스트(Unit Test)는 가장 작은 단위의 테스트이다. 보통 클래스/메서드 단위로 테스트를 진행한다. 단위테스트의 특징은 아래와 같다.
- 로직이 제대로 작동하는지 확인하는 용도(로직 동작에서의 예외 확인)
- 의존성이 있는 모듈과의 상호작용없이 독집적인 테스트
- 즉각적으로 빠르게 테스트가능
- 하나하나 독립적으로 잘 동작하는지에 대한 보장은 가능하지만, 의존성 있는 로직들이 상호작용했을때 잘 동작한다는 것을 보장할 수 없다!
통합테스트(Integration Test)
통합테스트(Integration Test) 모듈 간에 상호작용, 호환성에 문제가 없는지에 대한 테스트이다. 단위 테스트에서 확인하기 어려운 버그를 찾아낼 수 있다.
- 외부 라이브러리까지 묶어서 검증 가능하다.
- 단위테스트보다는 더 많은 범위를 한 번에 테스트한다 -> 신뢰성/정확성이 떨어짐
- 프로그램 자체가 완전하게 작동하는 걸 보장하지는 않는다.
기능테스트(Functional Test)
기능테스트(Functional Test)는 E2E(End-to-end), Acceptance, UI Test 등으로 불린다. 조금씩 차이는 있겠지만 공통적으로 실제 시나리오에 초점을 두고 결과물에 대한 테스트를 한다.
- 실제 요구사항대로 잘 동작하는지 검증(하나의 전체적인 기능이 원하는대로 잘 동작하는지 사용자의 관점에서 테스트)
- 내부 기능 하나하나 테스트 할 필요 없다.(단위 테스트의 역할)
- 테스트를 만들기 힘들고 오직 결과값만 신뢰 가능하다.
Testing Library - Jest
Jest는 페이스북에서 만든 테스팅 라이브러리다. javascript를 사용하는 프론트엔드뿐만 아니라 백엔드에서도 사용하고 있는 인기 있는 테스팅 라이브러리다. Jest로 소개함과 동시에 테스트를 위해 무엇이 필요한지 어떻게 사용하는지 알아보자.
테스트를 위한 필요조건
테스트코드를 실행하기 위해 필요한 존재들이 많다. 테스트 작성을 위한 객체, 테스트 실행, 의존되어있는 모듈의 가짜 객체 생성, 테스트 결과에 대한 확인 등에 필요한 도구를 나누자면 아래와 같다고 할 수 있다.
- Test Runner (테스트 객체 생성 및 실행, 결과를 보여주는 프로그램)
- Test Assertion/Matcher (요구사항에 대한 적합성 확인)
- Test Double (대체할 수 있는 가짜 객체 - Dummy, Stub, Spy, Mock, Fake 등 존재)
Jest의 특징점
- Runner, MatCher, Mock을 위한 모든 도구가 포함된 올인원 테스트 라이브러리이다.
- 잘 정리된 공식문서와 많은 레퍼런스가 존재한다.
- 쉬운 러닝커브
- 커스터마이징이 어렵다는 단점이 있다
Given-When-Then 패턴
꼭 필요한 패턴이라고 말 할 순 없지만 이 패턴으로 테스트를 쉽게 이해할 수 있다고 생각한다. Given-When-Then
패턴은 이름에서도 알 수 있듯이 세 부분으로 나뉘어 진다. 테스트 코드 작성 시 준비-실행-검증으로 나누는 것이다.
Given
- 준비과정 필요한 변수, 입력값 Mock 객체들을 설정, 함수에 Return될 값 또한 지정한다.
When
- 함수를 실행한다.
Then
- 실행 결과값을 검증한다.
Test Runner
Test 코드 객체 생성 및 실행이 가능하도록 도와주는 도구이다. Java에서는 JUnit, JS에는 Mocha, jest 등이 있다. jest에서는 테스트 객체를 생성할때 test()
혹은 it()
함수를 이용한다. describe()
함수를 이용하면 연관된 여러 테스트 함수들끼리 그룹화가 가능하다.
beforeEach()
, afterEach()
메서드는 각 테스트코드를 실항 전/후에 초기화할 코드를 작성한다. 하나의 테스트의 결과가 다른 테스트에 영향을 주는 것을 막는다.
describe("simple test", () => {
let testVar;
beforeEach(() => {
testVar = 1;
});
afterEach(() => {
testVar = 0;
});
test('첫번째 테스트', () => {
expect(1).toBe(1);
});
it('두번째 테스트', () => {
expect(testVar).toEqual(1);
});
})
Test Assertion/Matcher
다양한 방식으로 결과값을 테스트할 수 있도록 여러 방법들을 제공해주는 도구이다. jest에서는 expect()
메서드에 결과 값을 주고 expect()
뒤에 붙는 Matcher 함수를 통해 검증합니다.
예시에서 사용된 toBe()
메서드는 값이 같은지를 비교하는 Matcher이다.
test('첫번째 테스트', () => {
expect([]).toBe(1);
});
Jest - 자주사용하는 Matcher
Jest - Matcher
Test Double
하나의 기능을 테스트시 연관되어 있는 객체에 대해 가짜 객체를 만들어 주는 역할을 한다. Dummy, Fake, Stub, Spy, Mock 객체가 있으며 각각 조금씩의 차이가 있다. 이 블로그에서 자세하게 설명해주고 있다.
jest에서 Mock 생성 방법은 아래와 같다.
jest.fn
: 함수 Mock 생성jest.mock
: 객체와 모든 내부요소의 Mock 생성jest.spyOn
: 함수(Mock 아님)의 호출여부 및 호출방식만을 알아야할때 사용
jest의 Mock 함수가 가지는 메서드
mockReturnValue
: 리턴 값(가짜값) 지정mockImplemetation
: 함수를 즉석으로 구현(동작 Mock 함수만들기)mockResolvedValue
: Promise의 resolve를 반환받는 값 지정mockRejectedValue
: Promise의 reject를 반환받는 값 지정
댓글남기기