Skip to content

[9팀 임규원] Chapter 3-1. 프론트엔드 테스트 코드#51

Open
q1Lim wants to merge 18 commits intohanghae-plus:mediumfrom
q1Lim:medium
Open

[9팀 임규원] Chapter 3-1. 프론트엔드 테스트 코드#51
q1Lim wants to merge 18 commits intohanghae-plus:mediumfrom
q1Lim:medium

Conversation

@q1Lim
Copy link

@q1Lim q1Lim commented Aug 21, 2025

Medium

7주차 과제 체크포인트

기본과제

Medium

  • 총 11개의 파일, 115개의 단위 테스트를 무사히 작성하고 통과시킨다.

질문

Q. medium.useEventOperations.spec.tsx > 아래 toastFn과 mock과 이 fn은 무엇을 해줄까요?

  • enqueueSnackbarFn : 가짜 함수 (spy)로 실제로 토스트를 띄우는 대신, 어떤 메시지로 몇 번 호출되었는지 기록만 합니다.
  • vi.mock('notistack'...): useSnackBar()를 바꿔치기 해서 내부의 enqueueSnackbar가 toastFn을 가리키게 만듭니다.
// "호출되었는지, 어떤 인자로 호출되었는지"를 추적할 수 있는 mock 함수
const enqueueSnackbarFn = vi.fn();

// notistack 모듈을 통으로 mock하겠다
vi.mock('notistack', async () => {
  // 실제 모듈(notistack)의 구현을 불러오자
  const actual = await vi.importActual('notistack');

  return {
    // 실제 모듈의 export를 그대로 펼쳐 넣고, 
    ...actual,
    useSnackbar: () => ({
      // useSnackbar 훅만 mock해서 호출을 추적할 수 있게함 - useSnackbar 훅만 덮어쓰기
      enqueueSnackbar: enqueueSnackbarFn,
    }),
  };
});

Q. medium.integration.spec.tsx > 여기서 Provider로 묶어주는 동작은 의미있을까요? 있다면 어떤 의미일까요?

setup()에서 ThemeProvider, CssBaseline, SnackbarProvider로 감싸는 이유는 테스트에서도 운영 환경과 동일한 컨텍스트/스타일/DOM의 변화 등 을 재현하기 위해서입니다.
특히 통합 테스트에서는 컴포넌트가 테마 토큰(useTheme, useMediaQuery, sx), 전역 스타일(CssBaseline), 토스트 컨텍스트(useSnackbar)에 실제로 의존하므로, 프로덕션과 동일한 조건에서 동작을 검증하는 게 중요합니다.

운영과 동일한 css, 컨텍스트를 제공하기 위함이라면 모든 테스트 파일에 들어가면 좋지 않을까? 라는 생각이 스쳐지나갔는데..
꼭 그렇지 않다고 보는 게 맞을 것 같습니다. 순수 함수나 작은 단위의 컴포넌트는 DOM이나 테마와 같은 컨텍스에 의존하지 않기 때문에 유닛 테스트와 같은 파일에서는 필요하지 않다고 생각이 들었습니다.

Q. handlersUtils > 아래 여러가지 use 함수는 어떤 역할을 할까요? 어떻게 사용될 수 있을까요?

  • server.use : MSW 의 Node 테스트 서버에 요청 핸들러를 런타임에 추가/덮어쓸 수 있는 함수입니다. Node.js 런타임(=프로세스)가 살아있는 동안 서버 인스턴스에 계속 남아있어서 테스트 프로세스가 종료될까지 유지할 수 있습니다.

medium모드라 작성해주신 setup 함수를 살펴보면, 총 3가지로

  • setupMockHandlerCreation
  • setupMockHandlerUpdating
  • setupMockHandlerDeletion

CUD에 필요한 목업데이터 생성 -> server.use를 통해 핸들러를 등록/대체 -> 성공/실패/에러 응답에 대한 시뮬레이션에 사용 가능하게 작성해주셨습니다 ㅎㅎ

프로세스가 종료될 수 있을 때까지 덮어쓴 채로 유지되기 때문에 setupTest.ts에 server.resetHandlers()를 afterEach에서 호출해서 다음 테스트로 넘어갈 때 적용되지 않도록 반영해야합니다.

코치님이 설정해주신 setupTest.ts 파일 부분

afterEach(() => {
  // 각 테스트 후 핸들러 리셋시키기
  server.resetHandlers();
  vi.clearAllMocks();
});

Q. setupTests.ts vi.setSystemTime(new Date('2025-10-01')) > 왜 이 시간을 설정해주는 걸까요?

테스트의 오늘(now!)를 고정해 날짜/시간에 의존하는 로직을 '어디에서 혹은 누가' 돌려도 동일하게 재현하기 위해서라고 생각합니다.
사실 이건 테스트 작성하다가 놓쳤던..! 부분입니다. Event 객체를 만들어주는 함수를 만들면서 날짜를 8월에 맞게 진행했었는데 new Date()를 사용하다보면 2025-10-01로 고정이 되어있어서 설정을 찾다가 이걸 발견했습니다. (ㅎㅎ)

Q. setupTests.ts - expect.hasAssertions() > 설정하는 이유 ?

이 메서드에 대해 찾아보니, '이 테스트 안에서는 최소 1개의 expect가 반드시 호출되어야한다'를 보장하는 메서드였습니다.
비동기/타이머/이벤트 기반 검증에서 단언이 "실행되지 않고 지나가는" 실수를 자동으로 적발해 주는 안전 장치용으로 사용하는 메서드라고 생각합니다.

심화 과제

  • App 컴포넌트 적절한 단위의 컴포넌트, 훅, 유틸 함수로 분리했는가?
  • 해당 모듈들에 대한 적절한 테스트를 5개 이상 작성했는가?

과제 셀프회고

팀원들이 작성한 테스트코드는 자주 접해봤는데 실제로 작성해보는 건 처음이었습니다. (모든게 처음이라니..TT) 우선 easy 파일을 작성해서 문법과 api를 익혔습니다. 이후에는 이 라이브러리는 어떤 구조로 돌아가는 가를 제일 먼저 이해하고, 테스트 환경을 구성하는 것에 대해 어떤 설정이 필요한지 중점적으로 학습하고자 코치님이 작성해주신 파일들을 위주로 본 것 같아요!
테스트 코드를 작성하면서 제일 헤매고 시간이 많이 소요되었던 부분은 통합테스트였습니다. 실제 테마/CSS 컨텍스트가 반영된 DOM 값을 어떤 쿼리로 접근할지에 대한 고민을 많이 했던 것 같습니다. 막힐 때는 AI 도움과 팀원분들 PR을 참고해, 시나리오에 맞는 API를 선택하며 작업했습니다.

기술적 성장

act vs await

  • await : 비동기 작업이 끝날때까지 기다리기 (eg. Promise)
  • act : React 상태 업데이트가 일으키는 렌더/이펙트가 모두 반영될 때까지 테스트를 트랜잭션처럼 감싸기
    -> hook의 유닛테스트 중 리액트의 상태나 이펙트, 렌더와 관련된 테스트는 act를 사용했고 서버 호출과 관련된 hook 혹은 통합 테스트에서는 await를 위주로 사용했습니다.

getByText vs queryByText

  • getByText : 존재가 확실히 기대할 때 사용, 없으면 바로 에러 throw -> 테스트가 즉시 실패한다
  • queryByText : 없으면 null 반환, 여러개 매칭되면 에러 throw
    -> 무조건 값이 있어야한다를 사용할때는 getBy를 사용했고, 값이 없어야한다를 사용할때는 queryByText를 썼습니다.

코드 품질

트러블슈팅
통합테스트에서 1초를 지날 경우 알람이 발생하는 것을 검증하는 알림 테스트를 작성중 , 다음과 같은 에러를 만났습니다.

Error: "setSystemTime" was called already and date was mocked. Reset timers using `vi.useRealTimers()` if you want to use fake timers again.
    at FakeTimers.useFakeTimers (file:///Users/gyuwonlim/Desktop/project/hhPlusFE6/front_6th_chapter3-1/node_modules/.pnpm/vitest@3.2.4_@types+node@22.8.1_@vitest+ui@3.2.4_jsdom@26.1.0_msw@2.10.3_@types+node@22.8.1_typescript@5.6.3_/node_modules/vitest/dist/chunks/vi.bdSIJ99Y.js:3653:31)
  • medium.integration.spec.tsx
...
beforeEach(() => {
  vi.useFakeTimers({ shouldAdvanceTime: true });
});

이미 setupTest.ts에서 전역으로 userFakeTimers를 사용하고 있는 상태인데, 또 FakeTimers는 사용하려고 보니 충돌이 발생하여 문제가 되었습니다. AI찬스로 advanceTimersByTime를 알게되어 적용했습니다.

  act(() => {
    // 가짜 타이머를 1초만큼 이동시키고, 그로 인해 발생하는 React의 상태업데이트/이펙트를 act를 통해 반영
    vi.advanceTimersByTime(1000);
  });

과제 피드백

(제가 작성한 테스트 코드 중) 모호하거나 애매했던 부분

useEventForm의 테스트의 효과성
useEventForm에 대한 테스트를 심화 과제로 추가한 이유는, 폼 훅 자체의 입력/설정 흐름도 검증할 필요가 있다고 봤기 때문이었습니다. 다만 작성 과정에서 useEventOperations(네트워크/서버 호출)와 timeValidation(순수 검증)에서 이미 다루는 내용과 겹치는 지점이 많아 “중복 테스트 코드이지 않을까?”라는 의문이 생겼습니다. 제 자신 스스로 내린 결론은 useEventForm 테스트는 범위를 폼 훅 고유 책임으로 한정의 의미로 추가하자! 라는 생각으로 테스트 항목을 추가했습니다.

목업데이터 관리
easy파일을 작성하면서 sampleEvents 배열을 하나하나씩 작성해서 사용했는데, 사용하다가 관리가 되지 않아 createEventMock 함수를 임의로 생성해서 활용했습니다. 다만 임시로 만들다보니, 이 함수를 통해 만들 수도 있지만 id나 중복 데이터에 대한 관리가 제대로 되지 않을 것 같다는 생각이 들었습니다. 실제 업무에서도 임시 데이터를 하나만 두고 활용해서 썼었는데, 평소에 실무에서 목업데이터나 테스트에 사용하는 관련 데이터들을 어떻게 관리하시는지 궁금합니다.

Copy link

@susmisc14 susmisc14 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요! AI 코드 리뷰어입니다. 🤖
7주차 과제: 캘린더 앱 리팩토링 및 테스트 코드 작성 Pull Request에 대한 리뷰를 완료했어요. 함께 코드를 더 발전시켜 볼까요?

Note

✨ 이번 PR 한 줄 요약
거대한 App 컴포넌트를 기능별로 분리하고, 견고한 테스트 코드를 작성하여 애플리케이션의 유지보수성과 안정성을 크게 향상시켰습니다.


🎯 핵심 리뷰 요약

가장 중요한 개선 포인트부터 빠르게 확인해 보세요.

  • [CRITICAL] 컴포넌트 분리는 훌륭하지만, 그로 인해 발생한 Prop Drilling은 새로운 유지보수 문제를 야기할 수 있습니다.
  • [WARNING] 테스트 코드의 반복적인 상호작용을 헬퍼 함수로 추상화하여 가독성과 재사용성을 높일 수 있습니다.
  • [OPTIONAL] vi.mock과 같은 테스트 도구의 강력한 기능을 더 깊이 이해하고 활용하는 방법을 제안합니다.

🔍 상세 리뷰

각 항목에 대한 구체적인 설명과 코드 예시를 준비했어요.

1. 아키텍처: Prop Drilling과 상태 관리의 딜레마 (CRITICAL 🚨)

  • 👍 좋은 점: App.tsx에 있던 모든 UI와 로직을 EventFormView, CalendarView, EventList 등 여러 컴포넌트로 분리한 것은 정말 훌륭한 결정입니다. 각 컴포넌트가 단일 책임을 갖게 되어 코드의 가독성과 재사용성이 비약적으로 상승했습니다. 이는 관심사 분리(Separation of Concerns) 원칙을 아주 잘 적용한 사례입니다.

  • 💡 개선 제안: 컴포넌트 분리의 결과로 App 컴포넌트는 수많은 statehandler를 하위 컴포넌트에 props로 전달하는 역할을 하게 되었습니다. 이것이 바로 "Prop Drilling" 현상입니다. 예를 들어, saveEvent 함수는 App -> OverlapDialog로 전달되는데, 만약 중간에 다른 컴포넌트가 더 있었다면 그저 prop을 전달하기 위해 중간 컴포넌트들도 코드를 수정해야 했을 겁니다.

  • 🤔 이렇게 제안하는 이유: Prop Drilling은 컴포넌트 간의 결합도(Coupling)를 높입니다. 상위 컴포넌트의 상태 구조가 변경되면 수많은 하위 컴포넌트들이 영향을 받게 되죠. 이는 리팩토링을 어렵게 만들고, 컴포넌트의 재사용성을 저해하는 원인이 됩니다. "새로운 디자인 시스템 도입"이나 "새로운 기능 추가" 같은 변경사항이 발생했을 때, 현재 구조는 많은 파일을 수정해야 하는 어려움이 있습니다.

  • 💻 아키텍처 다이어그램 (AS-IS vs TO-BE)

    AS-IS: Prop Drilling 구조
    현재 구조는 App이 모든 상태와 로직을 소유하고, 필요한 모든 자식에게 일일이 전달하는 중앙집권적 형태입니다.

  graph TD
      App -- lots of props --> EventFormView;
      App -- lots of props --> CalendarView;
      App -- lots of props --> EventList;
      App -- lots of props --> OverlapDialog;
      App -- lots of props --> NotificationList;
Loading

TO-BE: Context API를 이용한 상태 공유 구조
Context APIZustand 같은 상태 관리 도구를 사용하면, Provider가 상태와 로직을 품고 있고, 필요한 컴포넌트가 중간 컴포넌트를 거치지 않고 직접 Context에 접근하여 상태와 로직을 사용할 수 있습니다. 이를 통해 컴포넌트 간의 결합도를 낮추고 유연성을 높일 수 있습니다.

  graph TD
      subgraph EventProvider
          direction LR
          State[Event State<br/>- events<br/>- editingEvent<br/>- ...]
          Actions[Event Actions<br/>- addEvent<br/>- deleteEvent<br/>- ...]
      end

      EventProvider --> EventFormView;
      EventProvider --> CalendarView;
      EventProvider --> EventList;
      EventProvider --> OverlapDialog;

      App --> EventProvider;
Loading

이 구조에서는 EventFormViewaddEvent 함수를 사용하고 싶을 때, App 컴포넌트로부터 props를 받을 필요 없이 EventContext에서 직접 가져와 사용할 수 있습니다.

2. 테스트 코드: 헬퍼 함수를 통한 가독성 향상 (WARNING ⚠️)

  • 👍 좋은 점: src/__tests__/utils.ts 파일에 createEventMock 팩토리 함수를 만드신 것은 정말 훌륭합니다! 각 테스트 케이스마다 거대한 모의 객체를 하드코딩하는 대신, 필요한 속성만 오버라이드하여 테스트의 의도를 명확하게 보여주고 있습니다. 이는 "테스트 코드의 간결성과 명확성" 원칙에 정확히 부합합니다.

  • 💡 개선 제안: medium.integration.spec.tsx를 보면, 일정을 저장하는 로직이 여러 테스트 케이스에서 반복되고 있습니다. 이미 saveSchedule라는 훌륭한 헬퍼 함수를 만들어 사용하고 계신데, 이 패턴을 다른 반복적인 상호작용에도 적용하여 테스트 코드를 더 구조화할 수 있습니다.

  • 🤔 이렇게 제안하는 이유: 테스트 코드는 애플리케이션 코드만큼이나 가독성과 유지보수성이 중요합니다. 사용자의 복잡한 상호작용을 별도의 함수로 추상화하면, 테스트 케이스의 본문은 "무엇을 테스트하는가"에만 집중할 수 있게 됩니다. user.type, user.click과 같은 구현 세부사항이 테스트의 핵심 의도를 가리는 것을 방지할 수 있습니다.

  • 💻 코드 예시 (AS-IS vs TO-BE)

    AS-IS: 반복적인 사용자 입력

  // src/__tests__/medium.integration.spec.tsx
  it('기존 일정의 세부 정보를 수정하고 변경사항이 정확히 반영된다', async () => {
    // ... setup
    const editButtons = within(eventList).getAllByLabelText('Edit event');
    await user.click(editButtons[0]);

    expect(screen.getByTestId('event-submit-button')).toHaveTextContent('일정 수정');

    await user.clear(screen.getByLabelText('제목')); // 반복되는 부분
    await user.type(screen.getByLabelText('제목'), '수정된 회의'); // 반복되는 부분
    
    await user.click(screen.getByTestId('event-submit-button'));
    // ... assertions
  });

TO-BE: 상호작용을 헬퍼 함수로 추상화
saveSchedule처럼, 폼 필드를 업데이트하는 헬퍼 함수를 만들어 재사용할 수 있습니다.

  // src/__tests__/medium.integration.spec.tsx
  
  // 헬퍼 함수 추가
  const updateFormField = async (user: UserEvent, label: string, value: string) => {
    const field = screen.getByLabelText(label);
    await user.clear(field);
    await user.type(field, value);
  };

  it('기존 일정의 세부 정보를 수정하고 변경사항이 정확히 반영된다', async () => {
    // ... setup
    const editButtons = within(eventList).getAllByLabelText('Edit event');
    await user.click(editButtons[0]);

    expect(screen.getByTestId('event-submit-button')).toHaveTextContent('일정 수정');

    await updateFormField(user, '제목', '수정된 회의'); // 추상화된 함수 사용

    await user.click(screen.getByTestId('event-submit-button'));
    // ... assertions
  });

3. 과제 질문에 대한 답변

과제에 남겨주신 질문들을 보니, 테스트 코드의 동작 원리를 깊이 있게 이해하려는 노력이 엿보여 인상 깊었습니다. 각 질문에 대해 답변해 드릴게요.

  • Q1. useEventOperations.spec.tsx > toastFnmock은 무엇을 하나요?

    • const enqueueSnackbarFn = vi.fn();은 Vitest가 제공하는 '모의 함수(Mock Function)' 를 만드는 코드입니다. 이 함수의 목적은 실제 기능을 실행하는 것이 아니라, "함수가 호출되었는가?", "어떤 인자와 함께 호출되었는가?" 를 추적하고 기록하는 것입니다.
    • vi.mock('notistack', ...)은 특정 모듈(notistack) 전체를 가짜로 대체하는 '모듈 모킹(Module Mocking)' 입니다. 테스트 환경에서는 실제 스낵바(토스트) UI를 띄울 필요가 없죠. 대신 useSnackbar 훅이 우리가 만든 가짜 함수인 enqueueSnackbarFn을 반환하도록 조작한 것입니다.
    • 결론: 이 두 코드를 통해 "우리가 예상한 특정 상황에서 enqueueSnackbar 함수가 정확한 메시지와 함께 호출되었는지"를 실제 UI 렌더링 없이 검증할 수 있습니다. 이는 테스트를 외부 의존성(UI 라이브러리)으로부터 격리시키는 중요한 기법입니다.
  • Q2. integration.spec.tsx > ChakraProvider로 묶어주는 것은 의미가 있나요?

    • 코드에서는 MUI의 ThemeProvider를 사용하고 계시지만, 원리는 같습니다. 네, 매우 의미 있는 동작입니다.
    • MUI나 Chakra UI 같은 컴포넌트 라이브러리는 내부적으로 Theme(색상, 폰트, 간격 등)에 의존합니다. 예를 들어, <Button color="primary">theme.palette.primary.main 값을 참조하여 색을 결정합니다.
    • 만약 ThemeProvider 없이 컴포넌트를 렌더링하면, 컴포넌트는 테마 값을 찾지 못해 에러를 발생시키거나 의도치 않은 스타일로 렌더링될 수 있습니다. 따라서 통합 테스트에서는 실제 애플리케이션과 거의 동일한 환경을 제공하기 위해 ThemeProvider, SnackbarProvider 같은 전역 Provider로 감싸주는 것이 필수적입니다.
  • Q3. handlersUtils > use 함수는 어떤 역할을 할까요?

    • setupMockHandlerCreation과 같은 함수들은 MSW(Mock Service Worker)의 핸들러를 설정하는 유틸리티 함수입니다. MSW는 API 요청을 가로채서 실제 서버 대신 우리가 정의한 모의 응답을 반환하게 해주는 라이브러리입니다.
    • setup... 함수는 특정 테스트 시나리오에 맞는 서버 상태를 만들어줍니다.
      • setupMockHandlerCreation([]): /api/events GET 요청에 빈 배열을 응답하여 "일정이 하나도 없는" 상태를 시뮬레이션합니다.
      • setupMockHandlerUpdating(): /api/events/:id PUT 요청에 성공 응답을 보내 "일정 수정이 성공하는" 시나리오를 만듭니다.
      • setupMockHandlerDeletion(): /api/events/:id DELETE 요청에 성공 응답을 보내 "일정 삭제가 성공하는" 시나리오를 만듭니다.
    • 이렇게 시나리오별로 핸들러를 분리하면, 각 테스트는 자신에게 필요한 서버 상태만 격리하여 설정할 수 있어 테스트의 명확성과 독립성이 크게 향상됩니다.
  • Q4. setupTests.ts > 왜 이 시간을 설정해주는 걸까요?

    • vi.setSystemTime(new Date('2025-10-01T00:00:00Z'))는 테스트 환경의 '현재 시간'을 특정 시점으로 고정하는 역할을 합니다.
    • 캘린더 앱은 '오늘'이 언제인지, 알림 시간이 되었는지 등 현재 시간에 따라 동작이 달라지는 기능이 많습니다. 만약 실제 현재 시간을 사용한다면, 테스트는 실행하는 날짜에 따라 성공하거나 실패할 수 있습니다. (예: 공휴일 테스트는 10월에만 성공)
    • 시간을 고정함으로써, 우리는 언제 누가 실행해도 항상 동일한 결과를 내는 예측 가능한(deterministic) 테스트를 작성할 수 있습니다. 특히 useNotifications처럼 "10분 후에 알림이 와야 한다"는 기능을 테스트할 때, 시간을 고정하고 vi.advanceTimersByTime()으로 시간을 인위적으로 흐르게 하여 정확한 동작을 검증할 수 있습니다.

🗺️ 전체 개선 로드맵 (우선순위 가이드)

피드백의 중요도 순서대로 정렬했어요. 높은 순위부터 차근차근 해결해 나가는 걸 추천해요.

  • CRITICAL 🚨: App.tsx의 Prop Drilling 문제 해결. (Context API 또는 상태 관리 라이브러리 도입 고려) 반드시 개선이 필요해요.
  • WARNING ⚠️: 통합 테스트(medium.integration.spec.tsx)에서 반복적인 사용자 상호작용을 헬퍼 함수로 추상화. 앞으로의 유지보수를 위해 개선을 적극 권장해요.
  • OPTIONAL 💡: EventFormView와 같이 props가 많은 컴포넌트의 props를 객체로 그룹핑하여 API를 더 명확하게 만들기. 코드 품질을 한 단계 더 높이기 위한 제안이에요. 선택적으로 적용해 보세요.

🤔 한 걸음 더: 코드와 의도 돌아보기

PR 본문의 회고는 비어 있었지만, 코드를 통해 깊은 고민의 흔적을 충분히 느낄 수 있었습니다. 심지어 심화 과제 체크박스는 비워두셨지만, 실제로는 App.tsx를 완벽하게 리팩토링하여 심화 과제를 성공적으로 수행하셨어요! 스스로의 성과를 더 자랑스럽게 생각하셔도 좋습니다.

  • 목표와 구현의 연결고리: 심화 목표였던 'App 컴포넌트 분리'는 이번 PR의 핵심적인 변화였네요. 혹시 컴포넌트의 경계를 어떻게 나눌지(예: WeeklyViewMonthlyViewCalendarView 안에 넣을지, 별도로 뺄지 등)에 대해 다른 대안을 고민하셨다면 무엇이었나요?
  • 숨겨진 의도 파악: 설명에는 없었지만, src/__tests__/utils.tscreateEventMock이라는 테스트 데이터 팩토리 함수를 추가하신 것을 발견했어요. 아주 좋은 변화라고 생각해요! 어떤 계기로 이 함수를 만들게 되셨는지 궁금하네요.
  • 스스로에게 던지는 질문:
    • "로직과 프리젠테이션(UI) 분리가 적절했나요?" (이번 리팩토링이 정확히 이 질문에 대한 훌륭한 답입니다.)
    • "Toss Frontend Fundamental의 결합도(Coupling) 원칙처럼, 컴포넌트 간 의존성을 낮추기 위해 어떤 고민을 했나요?" (Prop Drilling이 이 질문과 직결됩니다. Context API가 어떻게 결합도를 낮출 수 있을까요?)
    • "이 코드를 처음 보는 동료는 App.tsx에서 수많은 props가 내려가는 것을 보고 어떤 생각을 할까?"
    • "이번에 적용한 컴포넌트 구조가 미래에 '사용자별 캘린더 색상 설정' 같은 새로운 기능이 추가될 때도 유연하게 대처할 수 있을까?"

📚 참고 자료

문제 해결에 도움이 될 만한 자료들을 모아봤어요.

🌐 추천 아티클


이번 리팩토링과 테스트 코드 작성은 정말 인상 깊었습니다. 이미 좋은 개발자의 길을 걷고 계신 것 같아요. 이번 리뷰가 성장에 도움이 되었으면 좋겠습니다!

끝.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants