icon안동민 개발노트

단위 테스트 (Jest 사용)


 단위 테스트는 소프트웨어 개발에서 개별 코드 단위(주로 함수나 메서드)의 정확성을 검증하는 과정입니다.

 이는 버그를 조기에 발견하고, 코드의 품질을 향상시키며, 리팩토링을 용이하게 만듭니다.

 소프트웨어 개발 프로세스에서 단위 테스트는 개발자가 자신의 코드에 대한 확신을 갖게 하고, 지속적 통합(CI) 및 지속적 배포(CD) 파이프라인의 중요한 부분을 차지합니다.

Jest 소개

 Jest는 Facebook에서 개발한 자바스크립트 테스팅 프레임워크로, 간단한 설정, 빠른 실행 속도, 풍부한 기능을 제공합니다.

 다른 테스팅 도구들과 비교했을 때 Jest의 주요 장점은 다음과 같습니다.

  1. 설정이 거의 필요 없는 "배터리 포함" 접근 방식
  2. 병렬 테스트 실행으로 인한 빠른 성능
  3. 내장된 코드 커버리지 리포트
  4. 강력한 모킹 기능
  5. 스냅샷 테스팅 지원

Jest 설치 및 기본 사용법

 Jest를 설치하려면 다음 명령을 실행합니다.

npm install --save-dev jest

 기본적인 테스트 파일 구조는 다음과 같습니다.

// sum.js
function sum(a, b) {
  return a + b;
}
module.exports = sum;
 
// sum.test.js
const sum = require('./sum');
 
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

 Jest의 주요 문법 요소

  • describe : 관련된 테스트들을 그룹화
  • it/test : 개별 테스트 케이스를 정의
  • expect : 값을 검증

 예시

describe('Calculator', () => {
  it('adds two numbers correctly', () => {
    expect(sum(2, 3)).toBe(5);
  });
 
  it('subtracts two numbers correctly', () => {
    expect(subtract(5, 2)).toBe(3);
  });
});

비동기 코드 테스팅

  1. Callback
test('데이터 비동기 로드', done => {
  function callback(data) {
    try {
      expect(data).toBe('hello');
      done();
    } catch (error) {
      done(error);
    }
  }
  fetchData(callback);
});
  1. Promise
test('Promise 결과', () => {
  return fetchData().then(data => {
    expect(data).toBe('hello');
  });
});
  1. Async/Await
test('Async/Await 사용', async () => {
  const data = await fetchData();
  expect(data).toBe('hello');
});

모킹 (Mocking)

 모킹은 테스트 중에 외부 의존성을 대체하는 기술입니다.

 Jest에서는 다음과 같은 방법으로 모킹을 구현할 수 있습니다.

  1. jest.fn() : 함수 모킹
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);
 
expect(mockCallback.mock.calls.length).toBe(2);
expect(mockCallback.mock.results[1].value).toBe(43);
  1. jest.mock() : 모듈 모킹
jest.mock('./fetchData');
const fetchData = require('./fetchData');
 
fetchData.mockResolvedValue('mocked data');
 
test('mocked fetchData', async () => {
  const data = await fetchData();
  expect(data).toBe('mocked data');
});

테스트 커버리지

 Jest는 내장된 코드 커버리지 도구를 제공합니다.

 커버리지 리포트를 생성하려면 다음 명령을 실행합니다.

jest --coverage

 커버리지 결과는 다음 항목을 포함합니다.

  • Statements : 실행된 구문의 비율
  • Branches : 실행된 조건문 분기의 비율
  • Functions : 호출된 함수의 비율
  • Lines : 실행된 코드 라인의 비율

 높은 커버리지를 달성하기 위한 전략

  1. 테스트 우선 개발 (TDD) 적용
  2. 복잡한 로직에 대한 다양한 케이스 테스트
  3. 경계 조건 및 에러 케이스 포함
  4. 리팩토링 시 테스트 추가/수정

TDD (테스트 주도 개발)

 TDD는 테스트를 먼저 작성하고, 그 테스트를 통과하는 코드를 구현하는 개발 방법론입니다.

 Jest를 활용한 TDD 실천 방법

  1. 실패하는 테스트 작성
  2. 테스트를 통과하는 최소한의 코드 작성
  3. 코드 리팩토링
  4. 반복

 예시

// Step 1: 실패하는 테스트 작성
test('reverseString reverses a string', () => {
  expect(reverseString('hello')).toBe('olleh');
});
 
// Step 2: 테스트를 통과하는 최소한의 코드 작성
function reverseString(str) {
  return str.split('').reverse().join('');
}
 
// Step 3: 테스트 실행 및 통과 확인
 
// Step 4: 필요시 리팩토링 및 추가 테스트 작성

Best Practices 및 주의사항

  1. 테스트는 독립적이고 격리되어야 함
  2. 각 테스트는 하나의 동작만 검증해야 함
  3. 테스트 설명은 명확하고 구체적이어야 함
  4. 테스트 데이터는 실제 데이터와 유사해야 함
  5. 테스트 실행 속도를 고려해야 함
  6. 불필요한 중복을 피하고 테스트 유틸리티 함수 활용
  7. 스냅샷 테스트는 신중히 사용해야 함
  8. CI/CD 파이프라인에 테스트 통합
  9. 정기적인 테스트 코드 리뷰 및 리팩토링 수행

 Jest를 활용한 단위 테스트는 코드의 품질과 신뢰성을 크게 향상시킬 수 있습니다. 테스트 작성에 시간을 투자함으로써 장기적으로 버그 수정과 유지보수에 드는 시간과 비용을 절감할 수 있습니다. 또한 TDD 방식을 적용하면 더 견고하고 유지보수가 용이한 코드를 작성할 수 있습니다.

 Jest의 간편한 설정과 풍부한 기능은 개발자들이 쉽게 테스트를 시작하고 효과적으로 수행할 수 있게 해줍니다. 비동기 코드 테스팅, 모킹, 커버리지 분석 등의 고급 기능을 활용하면 복잡한 애플리케이션도 철저히 테스트할 수 있습니다.

 실제 프로젝트에서 Jest를 효과적으로 활용하기 위해서는 일관된 테스트 전략을 수립하고, 팀 전체가 테스트의 중요성을 인식하며 적극적으로 참여하는 문화를 조성하는 것이 중요합니다. 또한 CI/CD 파이프라인에 테스트를 통합하여 지속적으로 코드 품질을 모니터링하고 개선해 나가는 것이 좋습니다.

 마지막으로, 테스트 작성은 단순히 버그를 찾는 것 이상의 의미가 있습니다. 잘 작성된 테스트는 코드의 동작을 명확히 문서화하는 역할을 하며, 새로운 팀 멤버의 온보딩을 돕고, 코드의 의도를 명확히 전달합니다. 따라서 테스트 코드의 가독성과 유지보수성도 프로덕션 코드만큼 중요하게 고려해야 합니다.