back

Posts

사내 테스트코드 도입기

2024-01-05

작년 5월 이직 후, 회사에서 담당한 프로젝트를 지속적으로 리팩토링하고 기능을 추가하고 있었습니다. 개발을 진행하면서 테스트 코드 없이 로직을 검증하려니 생각보다 많은 시간이 소요되는 걸 느꼈고, 테스트 코드의 필요성을 절실히 깨달았습니다.

특히 힘들었던 부분들:

  • 특정 시간에만 오픈되는 UI 검증
  • 복잡한 가격 계산 로직 검증
  • 다양한 케이스별 분기 처리 확인

테스트 코드 없이 검증하려니 비효율적으로 낭비되는 시간과 노력이 너무 많았습니다.

다행히 다른 팀의 신규 프로젝트 준비로 잠시 여유가 생겨, 미루고 미뤄뒀던 테스트 코드를 도입하기로 결정했습니다.

1. Why Vitest?

처음에는 Jest를 사용하려 했지만, 최종적으로는 Vitest를 선택했습니다.

Vitest를 선택한 이유

항목JestVitest
초기 설정복잡한 설정 필요간단한 설정
속도상대적으로 느림매우 빠름
Vite 통합별도 설정 필요네이티브 지원
GUI별도 도구 필요@vitest/ui 제공

특히 @vitest/ui 라이브러리를 통한 GUI가 깔끔해서 개발 효율이 크게 증가했습니다.

레퍼런스 부족 문제는?

비록 Vitest 사용자가 아직 많지 않아 레퍼런스가 적지만, Jest와 90% 이상 유사해서 문제없었습니다. 막히는 부분이 있으면 ChatGPT의 도움으로도 충분히 해결할 수 있었죠.

2. 테스트 파일 위치 고민

테스트 코드를 작성하면서 가장 큰 고민은 테스트 파일을 어디에 둘 것인가였습니다.

첫 번째 시도: __tests__ 폴더

처음에는 __tests__ 폴더를 생성하여 하위에 모든 테스트 파일을 작성했습니다.

문제점:

  • 테스트 코드와 실제 파일의 위치가 너무 멀어 매번 스크롤이 필요했습니다
  • 원하는 테스트 파일을 찾는 데 시간이 걸렸습니다
  • 개발 효율이 떨어졌습니다

최종 선택: 하이브리드 방식

개발 효율을 위해 다음과 같이 변경했습니다:

  • __tests__ 폴더: 테스트 관련 유틸 및 설정 파일
  • 컴포넌트 옆: 각 컴포넌트의 테스트 파일은 바로 옆에 배치
components/
├── Button/
│   ├── Button.tsx
│   └── Button.test.tsx  ← 바로 옆에 위치
__tests__/
├── utils/
└── setup.ts

이 방식이 훨씬 개발하기 편했습니다!

3. 트러블 슈팅

문제 1: next/Image src 문제

Next.js의 Image 컴포넌트는 src를 다음과 같은 형태로 변환합니다:

원본: "testImage.com"
변환: "/_next/image?url=testImage.com&w=640&q=50"

따라서 이런 테스트는 실패합니다:

expect(image).toHaveAttribute('src', "testImage.com") // ❌ 실패

해결 방법: next/image 컴포넌트를 mocking하여 일반 img 태그로 변환합니다.

import { vi } from "vitest";

const mockNextImage = () => {
  vi.mock("next/image", () => ({
    __esModule: true,
    default: (
      props: React.DetailedHTMLProps<
        React.ImgHTMLAttributes<HTMLImageElement>,
        HTMLImageElement
      >
    ) => {
      return <img alt={props.alt} {...props} />;
    },
  }));
};

export { mockNextImage };

문제 2: Next.js Dynamic Import

Next.js의 dynamic을 사용해서 import한 컴포넌트들은 Vitest 테스트 환경에서 로드되지 않는 문제가 있습니다.

해결 방법: next/dynamic 자체를 mocking합니다.

vi.mock("next/dynamic", async () => {
  const dynamicModule: any = await vi.importActual("next/dynamic");
  return {
    default: (loader: any) => {
      const dynamicActualComp = dynamicModule.default;
      const RequiredComponent = dynamicActualComp(() =>
        loader().then((mod: any) => mod.default || mod)
      );

      // for debugging
      if (RequiredComponent?.render?.displayName) {
        RequiredComponent.render.displayName = loader.toString();
      }

      RequiredComponent.preload
        ? RequiredComponent.preload()
        : RequiredComponent.render.preload();

      return RequiredComponent;
    },
  };
});

정리

테스트 코드 도입 후 느낀 점:

장점:

  • 로직 검증 시간 대폭 감소
  • 리팩토링 시 안전성 확보
  • 버그 사전 발견 가능

배운 점:

  • Vitest는 빠르고 사용하기 편합니다
  • 테스트 파일 위치는 개발 효율을 고려해야 합니다
  • Next.js와 함께 사용할 때는 몇 가지 mocking이 필요합니다

여러분도 테스트 코드 도입을 고민 중이라면, Vitest를 추천합니다!