들어가며

테스트 코드는 항상 중요하다고 생각했지만, 실제로 적용하기에는 무겁게 느껴졌습니다. 하지만 이번 프로젝트에서 처음으로 본격적인 테스트 코드를 작성해보며 많은 것을 배우고 성장할 수 있었습니다.

테스트 코드 도입 과정

1. 테스트 전략 수립

처음에는 막막했습니다. 무엇부터 테스트해야 할지, 어떻게 접근해야 할지 감이 잡히지 않았습니다. 그래서 다음과 같은 단계적 접근법을 취했습니다:

  1. API 통신 테스트: 가장 먼저 외부 의존성을 가진 API 통신 부분 테스트
  2. 리덕스 상태 관리 테스트: 리듀서, 셀렉터, 사가 테스트
  3. 컴포넌트 테스트: 주요 UI 컴포넌트 동작 검증
  4. 통합 테스트: 여러 컴포넌트와 상태 관리가 함께 작동하는 흐름 테스트

2. 마주친 문제점들

비동기 테스트의 어려움

// 처음에는 이렇게 작성했으나 테스트가 불안정했습니다
fireEvent.click(screen.getByText('응시기간 설정'));
expect(screen.getByText('응시 기간 설정')).toBeInTheDocument();

// waitFor를 사용해 안정적인 테스트로 개선
fireEvent.click(screen.getByText('응시기간 설정'));
await waitFor(() => {
  expect(screen.getByText('응시 기간 설정')).toBeInTheDocument();
});

메모이제이션과 성능 최적화

테스트를 작성하다 보니 불필요한 리렌더링이 발생하는 부분을 발견했습니다.

// 최적화 전const dateInputClasses = `${styles.dateInput} ${isCalendarOpen ? styles.active : ''}`;

// 최적화 후const dateInputClasses = useMemo(
  () => `${styles.dateInput} ${isCalendarOpen ? styles.active : ''}`,
  [isCalendarOpen]
);

셀렉터도 메모이제이션이 필요했습니다:

// 최적화 전export const selectExamPeriod = (state: RootState) => ({
  startDate: state.examPeriod.startDate,
  endDate: state.examPeriod.endDate,
});

// 최적화 후 (메모이제이션 적용)export const selectExamPeriod = createSelector(
  (state: RootState) => state.examPeriod.startDate,
  (state: RootState) => state.examPeriod.endDate,
  (startDate, endDate) => ({ startDate, endDate })
);

코드 주석의 중요성

테스트 코드에서는 주석이 더욱 중요하다는 것을 깨달았습니다. 특히 특정 상태를 설정하거나 검증하는 단계에서 주석이 있으면 코드의 의도를 명확히 파악할 수 있습니다.

// API 성공 응답 설정
(projectsAPI.fetchProjects as jest.Mock).mockResolvedValue(mockProjects);

// Saga 실행await runSaga(...)

// 디스패치된 액션 검증expect(dispatched[0].type).toBe(fetchProjectsSuccess.type);