카테고리 없음

Test Code 작성 가이드 - 2021.05.28

지우개발자 2022. 4. 17. 04:34

2021.05.28

이 글은 프론트엔드 진영에서 테스트 코드를 어떻게 작성하면 좋을지에 대한 글 입니다.

목차

  • 서두
  • 좋은 테스트 코드
  • 선택과 집중
  • 작성법
  • 테스트 커버리지
  • 마치며

서두

우리는 모두 테스트 코드의 중요성을 알고 있다. 아직 테스트 코드의 중요성을 알지 못한다면 굳이 이 글을 읽는것을 추천하지 않는다. 중요성을 인지하지 못하고 작성하는 테스트 코드는 엉망진창으로 작성될 확률이 높고 코드를 작성하는 시간 자체가 노동일 뿐이다. 잘 작성된 테스트 코드가 가져올 효용을 알고있다면 코드를 작성하는 시간은 고되지만 미래를 생각하며 버텨낼 수 있다.

테스트 코드를 작성하는일은 고되다. 시간도 많이 들고 에너지도 많이 드는 일이다. 또한 테스트 코드 자체는 철옹성은 아니다. 테스트 코드의 장점은 작성하지 않았을때보다 적은 시간을 들이고 코드를 개선할 수 있다는것이다. 미래에 닥칠 재앙을 미리 준비하는 씨앗창고 같은것이다.

인터넷에는 다양하게 테스트 코드를 작성하는법이 나와있다. 하지만 그 글들은 말 그대로 코드를 작성하는 기술적인 부분만 설명 할 뿐 이외의 설명은 부족하다. 이 글은 테스트 코드를 작성하는 기술적인 부분 뿐만아니라 다양한 고찰또한 포함한다.

좋은 테스트 코드

  • 위에서 테스트 코드자체가 철옹성은 아니라고 말했지만, 좋은 테스트 코드는 철옹성 같은 테스트 코드이다. 어떤 상황에서도 완벽하게 예외처리를 한 테스트 코드는 좋은 테스트 코드이다.
  • 반면에 무너지기 쉬운 테스트 코드는 좋지 않다. 테스트 코드가 중심이 되고 실제 코드는 테스트 코드를 뒷받침 해 주어야한다. 실제 코드를 먼저 작성하고 테스트 코드가 해당 코드를 뒷받침하는 구조라면 코드가 바뀔때마다 테스트 코드도 항상 바뀌어야 하는 무너지기 쉬운 코드가 된다.
  • TDD는 테스트 코드를 우선적으로 작성 하자는 개발의 한가지 방법론 이지만 실무와는 거리가 먼 것으로 평가된다. 위에서 말한대로 테스트 코드가 중심이 되기 위해선 TDD로 코드를 작성하는것이 가장 좋다. 하지만 테스트 코드를 작성하는게 익숙하지 않다면 차선책으로 코드를 먼저 작성한 후에 테스트 코드를 써도 좋다. 다만 이 경우엔 테스트 코드를 많이 다듬는것이 좋다. 기존 코드 또한 다듬어진 테스트 코드에 맞춰 다듬는게 좋다. 테스트 코드를 단순히 끼워 맞추는것이 아닌 테스트 코드가 주도적으로 실제 코드를 이끌어가도록 많이많이 다듬는것이 중요하다.
  • 테스트 코드를 보는것으로 해당 함수가 어떠한 역할을 하는지 알 수 있도록 해야한다. 테스트 코드는 실제 함수를 추상화 할 수 있어야 한다. 함수의 내용을 볼 필요 없이 테스트 코드 만으로 해당 함수의 역할을 유추할 수 있으면 좋다.

선택과 집중

위에서 말했듯 테스트 코드를 작성하는 일은 매우 고된 일이다. 시간적으로든 정신적으로든. 테스트 코드의 커버리지를 100%로 가져간다면 매우 좋겠지만 실무에선 다양한 이유들로 그런 이상적인 상황은 많지 않을거라 생각한다. 테스트 코드의 커버리지가 낮다면 코드가 쌓일수록 대규모 리팩토링은 점점 위험해지게 된다.

우선은 작은 단위의 함수부터 작성하는것이 좋다. 큰 함수는 작은 함수들의 집합으로 이루어져있기 때문이다. 안전한 작은함수들이 있다면 그보다 큰 단위의 안전한 함수가 존재 할 수 있다.

그 다음은 본인의 맡고있는 서비스를 잘 이해하는것이 중요하다. 테스트 코드를 작성하는 가장 큰 이유는 코드의 변경이 있을때 안전하게 변경 할 수 있도록 도와주는 역할이기 때문이다. 본인이 맡고있는 서비스중 가장 변화 무쌍한 부분부터 테스트 코드를 작성해 나가면 좋다.

커버리지를 높히기 힘들다면 중요한 부분부터 커버해 나가면 되는것이다.

또한 굳이 테스트 코드가 아니더라도 코드를 보완해 줄수 있는 다양한 도구를 사용하는것도 방법이다. 예를 들면 리액트 컴포넌트 테스트를 대신해서 스토리북을 작성 한다던가, 타입스크립트를 사용해 자바스크립트의 타입 테스트는 하지않는 방법등이 있겠다.

작성법

사실 작성법은 이 글에서 가장 쓸모없는 부분이다. 테스트 코드를 작성하는 기술적인 부분은 테스팅 라이브러리 도큐먼트에 잘 설명되어 있고 인터넷에 자료도 많기 때문이다.

실질적으로 코드를 작성하는 방법보다는 개괄적으로 어떻게 설계해서 테스팅을 진행 할지에 대해 말하는것이 좋겠다.

TDD 접근법

1. 두가지 숫자를 더하는 함수를 만들고 싶다.
2. 숫자가 아닌 인자를 받는다면 에러를 뱉고 싶다.
3. 결과값이 소수라면 정수로 반올림을 하고싶다.

TDD방식으로 방수를 접근한다면 우선 위와같이 요구사항을 명시하고 시작하는것이 좋다. 위와 같은 요구 사항은 코드상 아래와 같이 표현 될 수 있다. (테스팅 라이브러리는 jest기준이다.)

const func = () => {}

describe('test add function', () => {
	it('add two arguments', () => {
		expect(func(10, 20)).toEqual(30)
	})

	it('if arguments type is not number, throw error', () => {
		expect(() => func('string', 20)).toThrow('error message')
	})

	it('if result is decimal, round result', () => {
		expect(func(1.1, 1.1)).toEqual(3)
		expect(func(1.5, 1.4)).toEqual(3)
	})
})

이후 요구 사항에 맞게 함수를 구현한다.

Mocking

함수가 순수하지 않다면 필연적으로 사이드 이펙트에 대해 모킹을 해야한다. 모킹은 번거로운 과정이고 많아질수록 해당 함수는 순수하지 않다는 뜻이된다. 때문에 모킹 작업이 많아서 테스트 코드를 작성하기 힘들다면 해당 함수는 잘못 작성되어 있을 가능성이 크다. 하지만 네트워크 요청이나 로컬 스토리지, 돔 등에 접근하는 사이드 이펙트는 막을 수 없다. 이럴 경우 필연적으로 모킹이 필요하다.

// repository.js
export const getProfile = () => { /* 네트워크 요청 후 Promise를 리턴하는 함수 */ }

// test.js
import * as repository from './repository.js'

jest.spyOn(repository, 'getProfile').mockReturnValue(Promise.resolve('something'))
// 이후 해당 함수는 테스트가 실행 될 동안 resolve value가 'something'인 Promise를 리턴한다.

jest.restoreAllMocks()
// 테스트가 끝났다면 기존 함수로 되돌려 놓는다.

모킹은 상당히 번거로운 작업이다. 따라서 모킹이 필요하다면 이 번거로운 작업을 대신해 줄 라이브러리가 있는지 조사부터 하는것이 좋다. 모든 함수를 일일히 모킹하는것은 바퀴를 다시 발명하는 일과 같기 때문이다. 예시로는 아래와 같은 npm 라이브러리가 있다.

  • Redux-Saga - redux-saga-test-plan
  • Axios - axios-mock-adapter
  • Date - mockDate
  • Redux - redux-mock-store

모킹은 검증된 라이브러리 사용을 적극 권장한다.

라이브러리를 사용한다면 사이드 이펙트를 글로벌로 모킹을 할 수 있다. 모킹에 대한 로직을 한군데에 정리해서 몰아넣고 실제로 테스트 하는 코드에는 사이드 이펙트에 대한 고민 없이 코드를 작성해 나갈 수 있다.

또한 서버에서 데이터를 받아오는 사이드이펙트라면 데이터의 인터페이스 또한 모킹을 해서 정리 할 수 있다. 모킹이 잘 되어있을 수록 테스트 코드를 작성하기 좋다.

위의 내용을 정리하자면 아래와 같은 순서로 테스트를 작성한다면 가장 이상적이다.

<aside> 💡 사이드 이펙트에 대한 모킹 → 구현할 함수의 추상화 → 실제 함수 구현

</aside>

테스트 커버리지

테스트 코드를 작성 하기란 고된 일이다. 그래도 다행스러운 점은 테스팅 라이브러리 또한 많은 발전이 있었다는것이다.

테스트 커버리지란 말 그대로 현재 작성되어 있는 코드가 어느정도 코드를 커버하는지에 대한 지표이다. 이 지표는 단순히 코드의 커버리지만 나타낼 뿐이고 100%라고 해서 완벽한 테스트 코드는 아니다. 하지만 얼마나 많은 테스트 코드가 작성되어 있는지에 대한 지표로는 충분하다.

테스트 커버리지는 아래의 세가지 기준으로 측정된다.

  • 구문(statement)
  • 조건(condition)
  • 결정

커버리지의 자세한 측정법은 여기 블로그에 잘 소개되어 있다.

jest coverage

jest는 현재 작성되어 있는 커버리지를 측정 할 수도 있다. 스크립트를 실행 시키면 위와 같이 정리된 html파일을 얻을 수 있다. 현재 작성되어 있는 각각의 파일과 폴더가 어느정도의 커버리지를 갖고 있는지 볼 수 있다.

또한 각 파일별로 해당 함수의 커버리지를 라인별로 볼 수 있다. 커버리지 파일에서 각각의 색상이나 숫자, 기호들이 무엇을 의미 하는지는 여기에서 확인 할 수 있다. 간단하게는 색상으로 칠해져 있다면 실행이 되지 않은 구문이기 때문에 더 다양한 테스트 케이스를 작성해야 함을 의미한다.

또한 이러한 커버리지를 눈으로 볼 수 있는 vscode extension또한 존재한다. coverage gutters

마치며

테스트 코드를 작성 하기란 고된 일이다. 몇번이고 말할만 하다. 하지만 잘 작성된 테스트 케이스는 든든하다. 이 또한 몇번이고 말할만 하다. 개발자라면 단순한 구현을 넘어서 테스트 코드를 작성하는 일 까지가 자신의 일이라고 생각한다. 처음부터 잘 작성 하기란 힘들지만 모두 테스트 코드와 친해졌으면 좋겠다. 👍