[main project day 21] 테스트

이민선(Jasmine)·2023년 7월 17일
0

jest/React Testing Library로 달력 테스트코드 작성 계획 짜기

아무래도 라이브러리로 달력을 사용하는 것이 아니라 직접 알고리즘으로 짠 달력을 사용하는 것이기 때문에 테스트를 반드시 거쳐야 한다고 생각하고 있었다. 실제로 지금까지 달력으로 기능들을 구현하면서 수많은 버그들을 마주쳤다. 일단 내 눈에 보이는 화면 버그는 더 이상 없는 것 같지만, 내가 만든 소중한 예약 달력 기능이 안정적으로 유지되고 팀원들과 유저에게 신뢰를 받으려면 테스트코드는 반드시 작성해봐야겠다는 생각이 들었다.
그리고 오늘부터 테스트코드 작성 계획 짜기 시작!

음.. 근데 일단 테스트해야 한다고 마음은 먹었는데 뭐부터 테스트해야 하지?
일단 테스팅 라이브러리를 처음 사용해보는 것이기 때문에 달력에서 어떤 부분들을 테스트해야할지에 대해 브레인스토밍을 해봐야겠다.

보통 3가지를 테스트할 수 있다고 한다.

         유닛테스트 | 통합테스트 | E2E테스트
좁은 범위<-------------------------------> 넓은 범위

유닛 테스트

프로그램의 개별적인 유닛(개별 함수 또는 메서드)이나 컴포넌트가 의도한 대로 동작하는지 검증하는 테스트이다. 코드의 가장 작은 단위가 정확히 동작하는지 확인한다.

통합 테스트

여러개의 컴포넌트가 서로 상호작용하며 잘 동작하는지 확인하는 테스트이다. 통합 테스트는 유닛 테스트보다 범위가 크며, 여러개의 유닛이 함께 잘 동작하는지 확인한다.

E2E(End-to-End) 테스트

사용자의 행동을 모방하여 애플리케이션의 흐름을 모두 테스트하는 방법이다. 사용자가 경험할 수 있는 모든 시나리오와 이슈를 포착할 수 있다.

  • 컴포넌트들이 정상적으로 렌더링되는가
    지금 예약 폴더에 있는 컴포넌트 폴더에 7개의 함수형 컴포넌트들이 있다. 이 함수형 컴포넌트들이 렌더링이 잘 되는지 확인해볼 수 있겠다.

  • 헬퍼함수들이 잘 작동하는지 테스트
    캘린더를 반환하는 함수, 날짜를 2자리수로 채워주는 함수, string type으로 서버에서 받은 데이터를 객체로 변환해주는 함수 이렇게 3개가 있다. 이 헬퍼함수들이 의도한 값을 return하는지 테스트해볼 수 있겠다.

  • 예약 상태가 의도한 대로 동작하는지 테스트
    오늘 날짜를 관리하는 store, 유저가 클릭한 날짜를 관리하는 store, 서버에서 들어온 예약 날짜 데이터를 관리하는 store 이렇게 3가지 store가 의도한 대로 동작하는지 테스트 해볼 수 있겠다.

테스트 코드를 어디에 위치시키지?

페이지 단위로 테스트 코드 폴더를 두려고 한다.
이유는?

  • 유닛 테스트부터 진행할 것이기 때문에 어떤 페이지에 대한 테스트 코드인지를 명확하게 파악하기 가장 좋은 구조일 것으로 예상되기 때문이다.

일단 컴포넌트 렌더링이 잘되는지부터 테스트코드를 짜보자!
React Testing Library의 'render'함수를 이용하면 컴포넌트를 가상 DOM에 렌더링하여 렌더링 결과를 확인할 수 있다고 한다.

처음에 짠 테스트코드

import { render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import { rootReducer } from '../../../common/store/RootStore';
import BookingDates from '../components/BookingDates';

describe('BookingDates', () => {
  test('renders BookingDates component', () => {
    // store를 생성
    const store = configureStore({
      reducer: rootReducer,
    });

    render(
      <Provider store={store}>
        <BookingDates />
      </Provider>,
    );

    expect(screen.getByText(/예약 시작 날짜/i)).toBeInTheDocument();
    expect(screen.getByText(/예약 마감 날짜/i)).toBeInTheDocument();
  });
});

엉잉?
근데 테스트코드가 실패하는뎁쇼?

toBeInTheDocument와 같은 Jest DOM matcher 함수를 사용하려면 해당 matcher를 Jest에게 확장해줘야 합니다. 이는 @testing-library/jest-dom 패키지에 있는 extend-expect를 import하는 것으로 가능합니다.

import '@testing-library/jest-dom/extend-expect';


짜잔! 나의 첫 테스트 코드 통과!

  • render 함수를 이용하여 BookingDates 컴포넌트를 가상의 DOM에 렌더링한다. 이 때 Provider를 이용하여 BookingDates 컴포넌트에 store를 주입한다.
  1. screen.getByText 함수를 이용하여 BookingDates 컴포넌트가 렌더링한 결과물에서 "예약 시작 날짜"와 "예약 마감 날짜"라는 텍스트를 찾는다.

  2. toBeInTheDocument를 사용하여 위에서 찾은 텍스트가 실제로 DOM에 존재하는지 확인합니다. 이 함수는 찾은 엘리먼트가 HTML 문서에 존재하는지를 확인하는 matcher이다.

따라서 이 테스트 코드는 BookingDates 컴포넌트가 정상적으로 렌더링되며, 그 결과로 "예약 시작 날짜"와 "예약 마감 날짜"라는 텍스트가 화면에 나타나는지를 검증하는 테스트다. 이 테스트가 통과한다는 것은, BookingDates 컴포넌트가 이러한 조건을 만족한다는 것을 의미한다.

이제 다른 컴포넌트들도 테스트해보자.
startDate와 endDate를 클릭했을 때 clearReservationDates 액션을 dispatch하여 시작 날짜와 마감 날짜가 null 값이 되는지 확인하는 코드이다.

 // "시작 날짜 재설정" 버튼을 클릭합니다.
    fireEvent.click(screen.getByRole('button', { name: /시작 날짜 재설정/i }));

    // clearReservationDates 액션이 dispatch되었는지 확인합니다.
    store.dispatch(clearReservationDates());

    // 액션이 디스패치되었거나 상태가 업데이트되었는지 확인
    const state = store.getState();
    expect(state.reservation.startDate).toEqual(null);
    expect(state.reservation.endDate).toEqual(null);

helper function도 테스트해보자. 일단 제일 해보고 싶었던 달력 테스트!

import { makeCalendar } from '../makeCalendar';

describe('makeCalendar', () => {
  it('should generate a correct calendar for July 2023', () => {
    const firstDay = new Date(2023, 6, 1).getDay(); // July 2023 starts on Saturday
    const lastDate = new Date(2023, 6, 0).getDate(); // July 2023 has 31 days
    const result = makeCalendar(firstDay, lastDate);

    expect(result[0]).toEqual([0, 0, 0, 0, 0, 0, 1]); // July 2023 starts from Saturday
    expect(result[5]).toEqual([30, 31, 0, 0, 0, 0, 0]); // July 2023 ends at 31
  });
});


어라 fail?

  // 이번 달 마지막 날짜를 구함
  const lastDateOfThisMonth: number = new Date(year, month, 0).getDate();
  // 이번 달 1일이 무슨 요일인지 구함
  const firstDayOfThisMonth: number = new Date(year, month - 1, 1).getDay();

알고보니 makeCalendar 함수에 넘기는 firstDayOfThisMonth 인자의 month 숫자가 lastDateOfThisMonth보다 1만큼 작았다.

숫자를 하나 증가시켜서 테스트하니 통과!

36개월 분의 달력은 모두 유닛테스트를 통과했다.
혹여나 버그가 있을까봐 걱정했지만..
최소 향후 3년 동안은 내가 만든 달력 알고리즘을 신뢰할 수 있는 것으로 결론을 내렸다.

profile
기록에 진심인 개발자 🌿

0개의 댓글