Featured image of post Jest를 활용한 유닛 테스트

Jest를 활용한 유닛 테스트

소프트웨어 테스트란?

소프트웨어 테스트는 소프트웨어가 요구사항을 충족하는지, 결함이 없는지, 의도한 대로 작동하는지를 확인하는 것을 목표로 수행됩니다.

TDD

예를 들면 TDD에서 파생된 BDD(행위 주도 개발)는 테스트 코드를 작성할 때 수행할 행위에 대한 명세를 비 기술적인 방식으로 작성하여 명세상의 요구사항을 만족하고 있는지 파악하는데 큰 도움을 주기도합니다.

이러한 이유로 어떤 회사에서는 개발을 시작하기 전 기획서를 기반으로 요구사항을 테스트 코드에 옮겨놓는 작업부터 하는 곳도 있다고 들었습니다.

그리고 최근 접했던 개발자 원칙이라는 책에서 탁월한 성과를 내는 조직의 공통점심리적 안정감이라는 내용이 있었습니다.

심리적 안정감실수가 드러났을 때에도 처벌받거나 놀림 받지 않을 것 이라는 믿음이라고 하는데, 테스트는 조금 다른 관점에서 실수가 발생하지 않을 것 이라는 믿음을 통해 안정감을 느끼게되어 도전적인 시도를 많이하게 되었다는 의견이 많은 것 같습니다.

V 모델

이처럼 테스트는 소프트웨어가 단순히 요구사항을 충족하는지, 결함이 없는지, 의도하는 대로 작동하는지 확인하는 것을 넘어서 안정감 같은 부과적인 효과를 위해서라도 현재 가장 중요한 프로세스라는 평가도 있을 정도입니다.

Unit Test란?

테스트 피라미드

테스트의 시작점이라고도 할 수 있는 단위 테스트는 프로그래밍의 최소 단위(함수, 메서드, 객체)를 테스트하는 방법 입니다.

주요 목적은 각 단위가 예상대로 작동하는지 확인하는 것이며, 소프트웨어 개발 과정에서 매우 중요한 단계로, 코드의 결함을 조기에 발견하고 수정할 수 있도록 도와줍니다.

주요 특징

단위 테스트의 주요 특징은 아래와 같습니다.

  • 빠른 피드백(Quickly)
    • 코드를 수정한 직후에 피드백을 받을 수 있도록 빠르게 수행되어야 함
    • 단위 테스트는 매우 작은 코드 단위를 테스트하기 때문에 빠르게 실행됨
  • 독립성(Isolation)
    • 각 테스트는 다른 테스트와 독립적으로 실행되어 테스트 간의 상호작용이나 의존성을 최소화해야 함
  • 작은 검증 단위(Veridate)
    • 작은 단위를 검증해야 함
  • 자동화 가능성(Automatic)
    • 자동화되어 개발 과정에서 지속적으로 실행되어야 함
    • 따라서 반복적인 테스트 작업을 효율적으로 수행 가능해야 함
  • 개발자 주도
    • 개발자가 작성하고 유지 관리함

좋은 단위 테스트의 특징

좋은 단위 테스트는 다음과 같은 특징들을 갖습니다.

  • 독립적
    • 각 테스트는 다른 테스트와 독립적으로 실행되어야 함
    • 테스트가 서로에게 의존하게 되면 문제를 파악하기 어려워짐
  • 자동화 가능
    • 단위 테스트는 자동으로 실행될 수 있어야 함
    • 자동화된 테스트는 반복적으로 실행할 수 있어 지속적인 피드백을 제공할 수 있음
  • 반복 가능
    • 테스트는 언제든지 반복해서 실행할 수 있어야 하며, 실행할 때마다 동일한 결과를 제공해야 함
    • 외부 요인에 의한 변동이 없어야 함
  • 빠른 실행
    • 단위 테스트는 빠르게 실행되어야 함
    • 테스트 실행 시간이 길어지면 개발자가 자주 실행하기 어려워지고 피드백 속도가 느려짐
  • 명확한 목적
    • 각 테스트는 특정 기능이나 동작을 검증하는 명확한 목적을 가져야 함
    • 무엇을 테스트하는지 분명히 알 수 있어야 함
  • 가독성
    • 테스트 코드는 읽기 쉬워야 함
    • 다른 개발자가 테스트 코드를 보고 쉽게 이해할 수 있어야 하며, 테스트의 의도를 명확히 파악할 수 있어야 함
  • 설정 및 정리
    • 테스트 실행 전후에 필요한 설정(setup)과 정리(teardown) 작업이 명확히 정의되어 있어야 함
    • 이는 테스트 환경을 일관되게 유지하는 데 중요함
  • 단일 검증
    • 각 테스트는 하나의 동작이나 기능을 검증해야 함
    • 어떤 테스트가 실패했을 때 그 원인을 쉽게 파악할 수 있음
  • 신뢰성
    • 테스트는 항상 일관된 결과를 제공해야 함
    • 테스트 결과가 불안정하면 테스트의 신뢰성이 떨어짐
  • 의존성 최소화
    • 외부 시스템이나 데이터베이스와 같은 외부 의존성에 최소한으로 의존해야 함
    • 필요하다면 목(Mocks)이나 스텁(Stubs) 같은 테스트 대역을 사용하여 외부 의존성을 격리할 수 있어야 함

요약하면 좋은 단위 테스트는 소프트웨어 개발 프로세스의 효율성을 높이고, 코드의 신뢰성을 강화하며, 유지 보수성을 향상시키는 데 중요한 역할을 합니다. 참 어렵군요..😂

Jest란?

JS진영에서 많이 활용되는 테스트 도구로 Jest, Mocha 등이 있습니다.

Jest

그 중 JestNest.js, React에서도 표준으로 사용되고 있을 만큼 활용도가 높은 뿐만 아니라, 단위 테스트, 통합 테스트, 스냅샷 테스트 등 다양한 테스트 유형을 지원하며, 많은 기능들을 지원하고 있으므로 선택하였습니다.

환경 준비

Node.js 환경에서 Jest를 사용해보겠습니다. 이를 위해 환경부터 구성해야합니다.

node, npm는 설치되어 있다고 가정하고 진행합니다.

프로젝트 초기화

새 프로젝트 디렉토리를 만들고, 만들어진 디렉토리에 프로젝트 초기화를 수행합니다.

1
2
3
mkdir my-project
cd my-project
npm init

타입 스크립트는 npm tsc --init 명령을 수행하면 됩니다.

Jest 설치

프로젝트에 Jest를 개발 의존성(dev dependency)으로 설치합니다.

1
npm install --save-dev jest

설치가 완료되면 package.json 파일에 Jest 관련 설정을 추가합니다.

scripts 섹션에 테스트 스크립트를 추가하여 Jest를 실행할 수 있게 합니다.

1
2
3
4
5
{
  "scripts": {
    "test": "jest"
  }
}

동작 확인

실제 코드를 실행하여 동작을 확인해봅니다.

operations.js

1
2
3
const add = (a, b) => a + b;

module.exports = { add };

operations.test.js

1
2
3
4
5
const { add } = require('./operations');

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

실행 결과

명령을 실행하여 테스트를 수행해보겠습니다.

1
npm test

정상적으로 수행되는 것을 확인할 수 있습니다.

실행 결과

Jest 기본 함수

Jest를 사용하기위한 기본적인 함수들을 확인해보겠습니다.

테스트 함수

test, it

단위 테스트를 정의하는 함수로, 두 함수 모두 동일하게 동작하여 편한 것을 사용하면 되겠습니다.

테스트를 설명할 문자열과 테스트 본문을 포함하는 콜백 함수를 인자로 받습니다.

1
2
3
4
5
6
7
test('함수 설명을 입력해주세요', () => {
  expect(someFunction()).toBe(expectedValue);
});

it('함수 설명을 입력해주세요', () => {
  expect(someFunction()).toBe(expectedValue);
});

describe

테스트를 그룹화 하는 합수입니다.

관련된 테스트를 하나의 블록으로 묶어 정리할 수 있습니다.

1
2
3
4
5
6
7
8
9
describe('MyComponent', () => {
  test('renders correctly', () => {
    expect(true).toBe(true);
  });

  test('another test', () => {
    expect(false).toBe(false);
  });
});

기대값 설정 함수

테스트 본문에서 사용하며, 결과가 특정 값임을 확인하는 하게됩니다.

expect

테스트의 기대값을 설정하여, 다양한 매처(Matcher)와 함께 사용되어 값을 검증하게됩니다.

1
2
3
4
expect(value).toBe(expectedValue);
expect(array).toContain(item);
expect(object).toHaveProperty('propertyName');
expect(function).toThrow(error);

주요 매처

기본적으로 많이 활용되는 매처들은 아래와 같습니다.

  • toBe
    • 기본적인 일치 검사를 수행합니다. (엄격한 일치)
  • toEqual
    • 객체나 배열의 값을 비교합니다.
  • toBeNull
    • 값이 null인지 확인합니다.
  • toBeDefined
    • 값이 정의되었는지 확인합니다.
  • toBeUndefined
    • 값이 정의되지 않았는지 확인합니다.
  • toBeTruthy
    • 값이 true로 평가될 수 있는지 확인합니다.
  • toBeFalsy
    • 값이 false로 평가될 수 있는지 확인합니다.
  • toContain
    • 배열이나 문자열에 특정 값이 포함되어 있는지 확인합니다.
  • toHaveLength
    • 배열이나 문자열의 길이를 확인합니다.
  • toHaveProperty
    • 객체가 특정 프로퍼티를 가지고 있는지 확인합니다.
  • toMatch
    • 문자열이 정규 표현식과 일치하는지 확인합니다.
  • toThrow
    • 함수가 호출될 때 예외를 던지는지 확인합니다.

모킹 및 스파이

jest.fn

모킹 함수 생성에 사용됩니다.

함수 호출 여부, 호출 횟수, 인자 등을 추적할 수 있습니다.

1
2
3
4
const mockFunction = jest.fn();
mockFunction('arg1', 'arg2');
expect(mockFunction).toHaveBeenCalled();
expect(mockFunction).toHaveBeenCalledWith('arg1', 'arg2');

jest.mock

모듈을 모킹하여 외부 의존성을 대체할 수 있습니다.

1
2
3
jest.mock('axios');
const axios = require('axios');
axios.get.mockResolvedValue({ data: 'mocked data' });

jest.spyOn

객체의 메서드를 감시하여 호출 여부, 호출 횟수, 인자 등을 추적할 수 있습니다.

1
2
3
4
5
6
7
const obj = {
  method: () => 'real implementation',
};

const spy = jest.spyOn(obj, 'method');
obj.method('arg1');
expect(spy).toHaveBeenCalledWith('arg1');

훅 Hooks

beforeAll, afterAll

각 테스트 블록 전후에 한 번씩 실행됩니다.

1
2
3
4
5
6
7
beforeAll(() => {
  // 모든 테스트 전에 한 번 실행
});

afterAll(() => {
  // 모든 테스트 후에 한 번 실행
});

beforeEach, afterEach

각 테스트 전후에 실행됩니다.

1
2
3
4
5
6
7
beforeEach(() => {
  // 각 테스트 전에 실행
});

afterEach(() => {
  // 각 테스트 후에 실행
});

타임아웃 및 기타 설정

jest.setTimeout

테스트의 타임아웃 시간을 설정합니다.

1
jest.setTimeout(10000); // 10초로 타임아웃 설정

test.only

해당 테스트만 실행합니다. 주로 디버깅 용도로 사용됩니다.

1
2
3
test.only('only this test will run', () => {
    expect(true).toBe(true);
});

test.skip

해당 테스트를 건너뜁니다. 임시로 테스트를 제외하고자 할 때 사용됩니다.

1
2
3
test.skip('this test will be skipped', () => {
    expect(true).toBe(true);
});

기타

찾아보니 유용해 보이는 다른 기능들도 추가합니다.

커버리지 확인

테스트 커버리지를 확인할 수 있습니다.

1
npx jest --coverage

마무리

사실 이전 직장에서 테스트 코드의 부재로 인한 고통을 뼈져리게 느꼈었기 때문에 테스트의 중요성은 너무나 잘 알고 있습니다.

꼭 필요하다고 생각하고 있지만, 습관화가 안되어 아직까지는 잘 활용하지 못하고 있었는데 이번 기회를 시작으로 테스트를 잘 적용해보려고 노력해야겠습니다.

끝까지 읽어주셔서 감사합니다.😁