TDD & Jest

seul_velog·2023년 9월 25일
0
post-thumbnail

TDD(Test-Driven Development)

TDD는 '테스트 주도 개발'이라는 의미를 가진다. TDD는 소프트웨어 개발 방법론 중 하나로, 실제 코드를 작성하기 전에 테스트 코드를 먼저 작성하는 접근 방식을 말한다.

  • TDD는 일반적으로 유닛 테스트와 함께 사용되지만, 다른 테스트 수준 (통합 테스트, 기능 테스트 등)에도 적용될 수 있다.

유닛 테스트(Unit Test)

  • 유닛 테스트는 소프트웨어의 가장 작은 단위, 즉 '유닛'을 독립적으로 테스트하는 것을 의미한다.
  • 가장 작은 코드 단위(예: 함수, 메서드)를 독립적으로 테스트하는 방법이다.
  • 유닛 테스트의 주 목적은 코드의 각 부분이 예상대로 동작하는지 확인하는 것이다.
  • 이러한 테스트는 개발 초기 단계에서 발생하는 문제를 신속하게 식별하고 수정할 수 있게 해준다.

통합 테스트(Integration Test)

  • 통합 테스트는 여러 컴포넌트나 유닛이 함께 작동하는 방식을 검증하는 것을 목표로 한다.
  • 예를 들어, 서비스와 데이터베이스, 라이브러리, 외부 서비스 간의 상호작용을 테스트하는 것이 포함될 수 있다.
  • 유닛 테스트는 독립적인 작은 부분을 테스트하지만, 통합 테스트는 이러한 개별 컴포넌트들이 올바르게 통합되어 동작하는지를 확인한다. 즉 외부 시스템과의 통합 포인트에서 버그를 찾는 데 중점을 둔다.

기능 테스트(Functional Test)

  • 기능 테스트는 애플리케이션의 특정 기능이 사용자의 요구 사항과 일치하는지를 테스트한다.
  • 주로 사용자 관점에서의 시나리오나 기능이 제대로 작동하는지 확인하는 데 중점을 둔다.

E2E 테스트 (End-to-End Test):

  • 애플리케이션을 사용자의 관점에서 테스트한다.
  • 사용자가 애플리케이션을 실제로 사용하는 것처럼 전체 시스템 흐름을 테스트한다.
  • E2E 테스트는 종종 기능 테스트로도 간주되며, 두 용어가 교차로 사용되기도 한다. 그러나 기능 테스트는 보통 개별 기능의 테스트에 초점을 맞추는 반면, E2E 테스트는 전체 애플리케이션의 시작부터 끝까지의 흐름을 테스트한다.

✍️ 이러한 테스트 유형들은 서로 다른 목적과 범위를 가지며, 테스트 전략에 따라 적절한 시점과 방법으로 조합되어 사용된다고 한다. 🤔



TDD의 기본 원칙

(1) 실패하는 테스트 작성
아직 구현되지 않은 기능에 대한 테스트를 작성한다. 즉, 먼저 원하는 기능이나 수정사항에 대한 실패하는 테스트 코드를 작성한다.
이 테스트는 처음에는 실패할 것인데, 왜냐하면 실제 구현이 아직 이루어지지 않았기 때문이다.

// add.js
// 아직 이 함수는 구현되지 않음
export function add(a, b) {
  // 구현 부분
}
// add.test.js
import { add } from './add';

// 실패하는 테스트 작성
test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

// 테스트 실행 시 'add' 함수가 아직 구현되지 않았으므로 실패한다.

(2) 최소한의 코드 작성
실패하는 테스트를 통과시킬 수 있는 최소한의 코드를 작성한다. 즉, 복잡한 구현, 최적화, 추가 기능 등을 고려하지 않고 오직 테스트만을 통과시키는 목적으로 코드를 작성한다.(여기서 중요한 것은 테스트를 통과시키는 것에 초점을 맞추는 것이다.)

// add.js
// 최소한의 코드를 작성하여 테스트를 통과시킨다.
export function add(a, b) {
  return a + b;
}

(3) 리팩토링
코드가 테스트를 통과하면, 코드를 깔끔하게 정리하고, 중복을 제거하며, 코드의 품질을 향상시킨다.



TDD의 주요 장점

(1) 높은 코드 품질
TDD를 통해 작성된 코드는 대체로 더 안정적이며, 잠재적인 버그 발생 확률이 낮아진다.

(2) 리팩토링 용이
테스트 코드가 있기 때문에 코드 변경 시 안전하게 리팩토링을 진행할 수 있다.

(3) 명확한 요구 사항 이해
테스트를 먼저 작성하면 개발자가 요구 사항을 더 명확하게 이해하게 되며, 해당 기능의 목적과 동작 방식에 대한 통찰력을 얻을 수 있다.





Jest

Jest는 페이스북에서 개발한 JavaScript 테스팅 프레임워크로, 특히 React와 같은 프론트엔드 라이브러리/프레임워크에서 많이 사용된다.


시작하기

설치하기

npm install --save-dev jest
yarn add --dev jest

📌 TypeScript를 위한 추가적인 패키지 설치하기
yarn add --dev @types/jest
: Jest의 TypeScript 타입 정의

yarn add --dev ts-jest
:TypeScript 코드를 Jest에서 직접 테스트할 수 있게 해주는 도구

yarn add --dev jest-environment-jsdom
: Jest 테스트 환경에서 DOM API를 에뮬레이트하기 위한 설정. 웹 브라우저 환경에서 실행되는 JavaScript 코드를 Jest 환경에서 실행할 때 필요

yarn add --dev @testing-library/react
: React 컴포넌트를 테스트하기 위한 유틸리티. 이 라이브러리는 React 컴포넌트를 렌더링하고 결과를 쿼리할 수 있도록 도와줌

yarn add --dev @testing-library/jest-dom
: Jest에 DOM에 대한 사용자 친화적인 어설션( 특정 조건이 참인지 확인하는 데 사용되는 명령 또는 함수)을 추가. 이를 통해 DOM 요소의 특정 상태나 속성을 더 쉽게 테스트할 수 있음

yarn add --dev @testing-library/user-event
: 사용자의 이벤트(클릭, 입력 등)를 시뮬레이트하기 위한 도구. fireEvent보다 더 실제 사용자의 동작에 가까운 이벤트를 발생시킬 수 있음.


npm 스크립트 설정

// package.json
"scripts": {
   ...
    "test": "jest"
  },

테스트 작성

Jest는 기본적으로 .test.js , .spec.js , .test.ts , 및 .spec.ts 확장자를 가진 파일들을 테스트 파일로 인식한다.

따라서 TypeScript로 작성된 테스트 코드를 사용할 때, 해당 파일의 확장자를 .test.ts 또는 .spec.ts 로 지정하면 Jest가 자동으로 해당 파일들을 테스트 파일로 인식하고 실행한다! 😀

// functions.js
export function add(a, b) {
  return a + b;
}
// functions.test.js
import { add } from './functions';

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

테스트 실행

npm test
yarn test





jest.config.js

Jest 테스트 프레임워크에서 프로젝트의 테스트 설정을 정의하는 파일이다. 이 파일을 사용하여 Jest의 다양한 옵션과 설정을 구성할 수 있다.

jest.config.js에서 구성할 수 있는 일반적인 옵션

roots: Jest가 테스트 파일을 찾을 위치를 지정한다.
(ex. 테스트 파일이 src/tests/ 디렉토리에 있다면, roots: ["<rootDir>/src/tests/"] 로 설정)

testEnvironment: 테스트 환경을 정의한다. (ex. node 또는 jsdom 등)

transform: 특정 파일 확장자를 처리하기 위한 변환 모듈을 지정한다. (ex. TypeScript 파일을 Jest에서 직접 처리할 때)

testRegex 또는 testMatch: 테스트 파일을 찾기 위한 정규식 또는 패턴을 지정한다.

moduleFileExtensions: 처리할 파일 확장자 목록을 지정한다.

setupFiles 또는 setupFilesAfterEnv: 테스트 실행 전에 특정 스크립트를 실행하도록 설정한다.

moduleNameMapper: 모듈 경로 별칭을 지정한다. 특히, 경로 별칭이나 특정 파일 확장자 (ex. .scss 또는 .png)에 대한 목을 설정할 때 유용하다.

moduleNameMapper: {
  "^@components/(.*)": "<rootDir>/src/components/$1",  // '@components/Button' -> '/src/components/Button'
  "\\.(css|less|scss)$": "identity-obj-proxy"          // CSS 모듈을 위한 목
}

testPathIgnorePatterns: 테스트에서 무시해야 하는 경로 패턴 목록을 지정한다. (ex. ["/node_modules/", "/build/"] )

transformIgnorePatterns: 변환에서 무시해야 하는 파일의 패턴 목록을 지정한다. 이 설정은 주로 node_modules 내의 모듈을 변환할 필요가 없을 때 유용하다.

watchPlugins: 테스트 중에 사용할 수 있는 명령 및 패널 UI를 제공하는 외부 플러그인 목록을 지정한다.

collectCoverage: 코드 커버리지 정보를 수집할지 여부를 지정한다. 보통 테스트 커버리지를 확인하고 싶을 때 true로 설정한다.

coverageThreshold: 코드 커버리지의 최소 허용치를 지정한다. 설정된 임계값보다 낮은 경우, Jest는 에러 코드와 함께 종료된다.

coverageReporters: 커버리지 리포트 형식을 지정한다. 자주 사용되는 값으로는 ["lcov", "text"] 등이 있다.

collectCoverageFrom: 커버리지를 수집할 파일 목록을 지정한다. 특정 파일이나 폴더를 제외하고 싶을 때 사용한다.

setupFilesAfterEnv: 각 테스트 파일 실행 전에 실행되어야 할 설정 파일 목록을 지정한다. (ex. 테스트 라이브러리의 추가 설정이나 테스트 유틸리티를 로드하는 데 사용)

mocks, spies, stubs: 테스트에 사용될 모킹, 스파이, 스텁을 설정한다.


// ex.)
module.exports = {
  testEnvironment: "node",
  transform: {
    "^.+\\.tsx?$": "ts-jest"
  },
  testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
  moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
};

.ts.tsx 확장자를 가진 파일을 ts-jest 를 사용하여 변환하도록 설정하며, 테스트 환경으로 node 를 사용하도록 설정





모킹(mocking)

실제 구현이나 동작을 가진 객체, 함수, 모듈 등을 테스트를 위한 더미 객체로 대체하는 것이다.
주로 단위 테스트에서 외부 의존성을 가진 코드의 동작을 테스트할 때 사용된다.

  • 모킹은 단위 테스트에서 특히 중요하다. 단위 테스트는 보통 개별 코드 모듈이나 함수의 동작을 테스트하는데, 이 때 해당 모듈이나 함수가 의존하는 외부 요소들을 실제로 사용하면 테스트의 복잡성이 증가하고, 테스트 실패 원인을 찾기 어려워질 수 있다.
    이런 이유로, 테스트 대상 코드가 의존하는 요소들을 모킹해서 대체하게 된다.

  • 모킹을 통해 테스트 대상 코드의 동작을 더 잘 제어하고, 테스트의 독립성과 격리를 유지할 수 있다.
    또한, 모킹을 사용하면 실제 구현이 완료되지 않은 기능에 대한 테스트를 미리 작성하는 것도 가능하다.

  • 예를 들어, 네트워크 API 호출을 하는 함수를 테스트한다고 할 때, 실제 네트워크 호출을 하지 않고 그 결과를 모킹하는 것이 일반적이다.
    이를 통해 네트워크 연결 문제, 서버 문제 등 외부 요인으로 인한 테스트 실패를 방지하고, 함수가 API 응답을 올바르게 처리하는지에 초점을 맞출 수 있다.

  • JavaScript 및 TypeScript에서는 Jest와 같은 테스트 프레임워크를 사용하여 모킹을 쉽게 할 수 있다.
    Jest는 함수 모킹, 타이머 모킹, 모듈 모킹 등 다양한 모킹 기능을 제공한다.


✍️ 모킹의 주요 목적

(1) 의존성 제거
코드가 데이터베이스, 네트워크, 하드웨어 등의 외부 시스템에 의존할 경우, 이러한 의존성을 제거하고 테스트를 단순화하기 위해 모킹을 사용한다.

(2) 검증
테스트 중에 특정 함수나 메서드가 예상대로 호출되었는지 검증한다.

(3) 특정 시나리오 재현
예를 들어, 네트워크 오류 또는 데이터베이스 연결 실패와 같은 예외적인 상황을 재현하여 테스트할 수 있다.

profile
기억보단 기록을 ✨

0개의 댓글