Legitgoons 님의 상세페이지[1팀 이의찬] Chapter 3-2. 프런트엔드 테스트 코드

8주차 과제 체크포인트

기본 과제

필수

  • 반복 유형 선택
    • 일정 생성 또는 수정 시 반복 유형을 선택할 수 있다.
    • 반복 유형은 다음과 같다: 매일, 매주, 매월, 매년
      • 31일에 매월을 선택한다면 -> 매월 마지막이 아닌, 31일에만 생성하세요.
      • 윤년 29일에 매년을 선택한다면 -> 29일에만 생성하세요!
  • 반복 일정 표시
    • 캘린더 뷰에서 반복 일정을 시각적으로 구분하여 표시한다.
      • 아이콘을 넣든 태그를 넣든 자유롭게 해보세요!
  • 반복 종료
    • 반복 종료 조건을 지정할 수 있다.
    • 옵션: 특정 날짜까지, 특정 횟수만큼, 또는 종료 없음 (예제 특성상, 2025-06-30까지)
  • 반복 일정 단일 수정
    • 반복일정을 수정하면 단일 일정으로 변경됩니다.
    • 반복일정 아이콘도 사라집니다.
  • 반복 일정 단일 삭제
    • 반복일정을 삭제하면 해당 일정만 삭제합니다.

선택

  • 반복 간격 설정
    • 각 반복 유형에 대해 간격을 설정할 수 있다.
    • 예: 2일마다, 3주마다, 2개월마다 등
  • 예외 날짜 처리:
    • 반복 일정 중 특정 날짜를 제외할 수 있다.
    • 반복 일정 중 특정 날짜의 일정을 수정할 수 있다.
  • 요일 지정 (주간 반복의 경우):
    • 주간 반복 시 특정 요일을 선택할 수 있다.
  • 월간 반복 옵션:
    • 매월 특정 날짜에 반복되도록 설정할 수 있다.
    • 매월 특정 순서의 요일에 반복되도록 설정할 수 있다.
  • 반복 일정 전체 수정 및 삭제
    • 반복 일정의 모든 일정을 수정할 수 있다.
    • 반복 일정의 모든 일정을 삭제할 수 있다.

심화 과제

  • 이 앱에 적합한 테스트 전략을 만들었나요?

심화과제는 1팀 이의찬, 1팀 김휘린, 5팀 양성진 3명이서 함께 진행했습니다. 심화 과제 관련 내용은 5팀 양성진 PR에 함께 작성했습니다.


과제 셀프회고

  • 원래 이번 주는 TDD를 경험하는 것이 메인 주제였지만, 저는 지난 주에 과제를 정상적으로 진행하지 못해서 이번 주에 테스트 코드와 TDD를 모두 학습하려고 노력했습니다.
  • 그 덕분에 이번 주차에서 TDD를 경험하는 것 외에도 '접근성을 고려해서 좀 더 의미 있는 테스트를 작성하는 방법'과 이슈를 해결하며 적절한 테스트 작성 방법에 대해서 배울 수 있었습니다.

기술적 성장

Testing Library와 현실적 타협점 찾기

우선 테스트를 작성하면서 처음에는 각각의 반복 인스턴스가 개별 이벤트로 생성될 것이라고 예상하고 테스트를 작성했습니다.

expect(eventList.getByText('2025-01-31')).toBeInTheDocument();
expect(eventList.getByText('2025-03-31')).toBeInTheDocument();
expect(eventList.queryByText('2025-02-31')).not.toBeInTheDocument();

하지만 실제 구현에서는 반복 일정이 하나의 이벤트 객체로 관리되고 있었고, 이를 수정하기보다는 현재 구현에 맞춰 테스트를 조정하는 것이 더 현실적이라는 걸 배웠습니다.

expect(eventList.getByText('2025-01-31')).toBeInTheDocument();
expect(eventList.getByText(/반복:/)).toBeInTheDocument();
expect(eventList.getByText(/월마다/)).toBeInTheDocument();

"Found multiple elements" 해결하기

FormControlLabelCheckbox가 둘 다 같은 라벨을 가지면서 MUI 컴포넌트들이 복잡한 DOM 구조를 만들면서 발생하는 "Found multiple elements" 에러가 발생해 Testing Library가 혼란스러워했습니다.

이 과정에서 쿼리 우선순위를 체계화할 수 있었습니다.

  1. getByRole + 정규식
  • MUI는 접근성을 잘 지켜서 role이 명확하고, 정규식을 쓰면 텍스트 변화에도 유연하게 대응할 수 있습니다.
  • 실제 사용자가 스크린 리더로 접근하는 방식과 동일해서 가장 안정적입니다.
  1. getElementById
  • ID를 사용해서 확실하게 하나만 선택할 수 있습니다.
  • 하지만 MUI 컴포넌트들은 기본적으로 ID를 자동 생성하거나 없는 경우가 많아서 개발자가 직접 줘야 하는 추가 작업이 필요합니다.
  1. getByLabelText
  • MUI의 복잡한 DOM 구조 때문에 라벨 텍스트가 여러 곳에 중복으로 나타나서 Testing Library가 혼란스러워합니다.
  • 가장 예측하기 어렵고 불안정합니다.

그리고 특정 DOM 요소 안에서만 검색 범위를 제한하는 within()로 스코프를 제한하는 방법도 학습할 수 있었습니다.

  1. 특정 컨테이너를 먼저 찾기
const eventItem = screen.getByText('특정 이벤트 제목').closest('[role="article"]');
  1. 그 컨테이너 안에서만 검색하기
const deleteButton = within(eventItem).getByLabelText('Delete event');

이런 방법들을 통해서 반복 일정이나 여러 개의 동일한 컴포넌트가 있을 때 발생하는 "Found multiple elements" 에러를 해결했습니다.

TDD 경험

이번 과제의 핵심 중 하나가 테스트를 먼저 작성하고 이를 기반으로 개발하는 TDD를 체험해보는 것이었는데, 구현도 없는 상태에서 테스트부터 작성한다는 게 어색하게 느껴질 수 있지만 TDD는 AI 시대에 정말 유용한 개발 방법론이라고 생각합니다.

요구사항만 명확히 정리되어 있다면 이를 기반으로 "이런 테스트를 작성해줘"라고 요청하면 되니까 테스트를 작성하는 건 AI의 도움을 받기가 상대적으로 쉽고, 그리고 그 테스트를 기반으로 구현을 하는 것도 더 명확한 방향성을 가지기 때문입니다.

// 1단계: 실패하는 테스트 작성 (Red)
it('31일에 매월 반복을 선택하면 매월 31일에만 생성된다', async () => {
  // 요구사항을 명확히 한 테스트
  expect(eventList.getByText('2025-01-31')).toBeInTheDocument();
  expect(eventList.queryByText('2025-02-31')).not.toBeInTheDocument(); // 2월 31일은 없음
});

기본 과제 테스트 구현 전략

이번 기본 과제에서는 통합 테스트 위주로 테스트 코드를 작성했습니다.

  • 프론트엔드는 "기능 바꿔주세요"라고 하면 화면에서 바로 그 변화가 보여야 하는데, 그런 사용자 경험은 통합 테스트 단계에서 명시되다 보니 이 방식이 더 적절할 것이라고 생각했습니다.
  • 테오가 결국 문제는 액션에서 생긴다라고 말했듯이 유닛 테스트만으로는 "진짜 잘 작동하나?"에 대한 확신을 얻기에 모자란 점이 많다고 생각하기에, 기능이 정상적으로 동작함을 증명할 수 있는 통합 테스트가 우선적으로 작성되는 게 더 낫다고 느꼈습니다.

코드 품질

접근성 고려

data-testid를 제거하고 접근성 속성 기반으로 마이그레이션하는 과정에서, 테스트하기 쉬운 코드가 실제로 사용하기도 쉬운 코드라는 걸 체감했습니다. aria-label, role 같은 속성들이 단순히 테스트를 위한 게 아니라 실제 사용자 경험 향상에 기여한다는 점이 인상적이었습니다.

학습 효과 분석

도메인 로직의 복잡성 고려해서 테스트 작성하기

달력 관련 로직에서 31일 매월 반복, 윤년 2월 29일 처리 같은 것들이 단순해 보이지만 실제로는 정말 많은 예외 상황을 고려해야 합니다. 특히 이런 도메인 로직을 테스트할 때는 일반적인 케이스보다 예외 케이스가 더 중요할 수 있다는 걸 배웠습니다. 윤년이 4년에 한 번 오는 특수한 상황인데, 그런 상황에서의 사용자 경험이 실제로는 더 중요할 것 같습니다.

리뷰 받고 싶은 내용

과제 피드백

undefined