Medium
7주차 과제 체크포인트
기본과제
Medium
- 총 11개의 파일, 115개의 단위 테스트를 무사히 작성하고 통과시킨다.
질문
Q. medium.useEventOperations.spec.tsx > 아래 toastFn과 mock과 이 fn은 무엇을 해줄까요? notistack 라이브러리의 useSnackbar 훅을 목킹해준다. 테스트 환경에서는 실제 UI 스낵바를 띄우지 못하기 때문에 enqueueSnackbarFn이 호출되었는지, 어떤 메시지가 전달되었는지 확인하는 방법을 사용한다.
Q. medium.integration.spec.tsx > 여기서 Provider로 묶어주는 동작은 의미있을까요? 있다면 어떤 의미일까요? SnackbarProvider는 notistack에서 useSnackbar훅을 사용할 수 있게 만드는 컨텍스트 프로바이더이다. ThemeProvider는 MUI컴포넌트들이 theme를 받아서 올바르게 렌더될 수 있게 만드는 프로바이더이다.
Q. handlersUtils > 아래 여러가지 use 함수는 어떤 역할을 할까요? 어떻게 사용될 수 있을까요? server.use()로 msw 핸들러를 등록한다. 특정 엔드포인트에 대해 성공 혹은 실패, 그리고 리턴값까지 정해서 보내줄 수 있다. 테스트 환경에서 msw가 가로챌 엔드포인트들을 설정한다.
Q. setupTests.ts > 왜 이 시간을 설정해주는 걸까요? 모든 테스트가 기준날짜를 사용하도록 보장하기 위해서 설정한다. 타임존의 차이나 실제 실행시점이 달라서 발생하는 테스트케이스를 제거하기 위해서다. 일정이나 알림 반복과 같은 시간을 기반으로 하는 로직을 안정적으로 검증할 수 있다.
심화 과제
- App 컴포넌트 적절한 단위의 컴포넌트, 훅, 유틸 함수로 분리했는가?
- 해당 모듈들에 대한 적절한 테스트를 2개 이상 작성했는가?
과제 셀프회고
이번에 Medium을 선택했다. 나는 테스트코드에 첫 도전이기에 괜히 무리하다가 배우지도 못할바엔 처음부터 안정적으로 가야겠다고 생각했다.
처음 easy 난이도의 테스트들을 작성할 때, 생각보다 괜찮은데? 라는 생각으로 쉽게 작성했다. 유닛테스트들이 워낙 순수함수이면서 기능이 명확하다보니 헷갈리는 지점이 적었다.
하지만 hook을 테스트하면서 부터 약간 헷갈렸다. 어디까지 검증을 해줘야하지? 하는 의문이 들기 시작했다.
그러다 통합테스트를 시작하는데, 어떤 테스트 메서드를 사용해야하는지 잘 몰라서 초반에 많이 헤맸다. 하지만 오히려 통합테스트는 조금 적응하고 나니 작성하는게 재미있고 쉬웠다. 마치 내가 QA를 하는것처럼 이런 케이스는 이렇게 검증해야지 하는 생각을 먼저 한 뒤 코드로 옮기면 쉽게 끝낼 수 있었다.
뭔가 아직 적응은 덜 된것같지만.. 앞으로 계속 작성해 나가다 보면 나도 쉽게 할 수 있을거라는 확신이 든다.
기술적 성장
일단 테스트코드 자체의 사용 방법은 이번 과제에서 처음이기 때문에 기술적 성장이 되었다고 생각한다. 그리고 도대체 프론트에서 테스트 코드는 과연 어떤 부분을 보는 것일까? 라는 막연한 생각이 있었는데 순수 util함수의 unit테스트, 각 hook의 테스트, 컴포넌트 test, 그리고 통합 테스트까지 모두 겪어보면서 감을 익혔다.
코드 품질
이 부분은 만족스럽다기보다 고민이 좀 많이 들어갔고 아직도 궁금한 부분들이 있다. 바로 목킹할 api를 모두 핸들러에 빼둔것인데, 이 부분에 대해 단점도 있다고 생각이 들었다. 바로 한 눈에 어떤 데이터가 포함되어 있는지 모른다는 것이다. 그래서 수정이나 삭제를 할 때, 혹은 get을 해올때도 정확히 어떤 데이터가 있는지 다시 목킹함수를 봐야만 했다.
이와 비슷하게 목킹함수가 필요없고 그저 목데이터만 필요한 부분도, 상단에 변수처리 후 하위에서 사용하면 위와 비슷한 문제를 겪을 수 있었다.
학습 효과 분석
테스트코드와 익숙해지기, 프론트에서 테스트코드를 전반적으로 훑어보기 정도만 생각하면 이번 과제를 통해 아주 성공적이였던것 같다. 물론 모든 테스트 메서드를 아는것은 아니지만, UI를 찾을때 대략 어떤걸 사용해야하는지, 이벤트를 발생시킬때 어떤것을 사용해야하는지, 검증을 할때 어떤것을 사용해야하는지 등등 조금씩만 알아도 많은 부분을 테스트 할 수 있었다.
과제 피드백
이번 과제에서 모호했던 부분 중 하나는 통합 테스트이다. 과연 통합테스트는 어디까지 사용자 관점으로 봐야하는것인가? 하는 생각이 들었다.
그 중 한 예시로, notification 시간이 다가왔을때, 토스트 알림이 뜨는 통합테스트가 있었는데, 내가 직접 추가하고 그 시간이 도달했을때 라고 가정을 해야하는지.. 아니면 처음부터 그 테스트가 있고 시간만 흘려 보낸 뒤 알림을 확인해야하는지.. 아니면 처음부터 알림시간에 맞춰 테스트를 진행해도 되는지.. 이런 고민들이 오갔다.
나는 결국 처음부터 테스트가 존재하지만, 알림까지 시간을 흘려보내준 뒤 알림이 존재하는걸 확인하는 방향으로 선택했다. 이미 앞서 추가하는것도 통합테스트를 했는데 굳이 중복해서 거기까지 만들 필요가 없어보였기 때문이다.
하지만 아직도 어떤 방향이 더 옳은지는 잘 모르겠다.
리뷰 받고 싶은 내용
질문 1. MUI의 Select컴포넌트의 option은 react portal로 열리는것을 확인했습니다.
it('주별 뷰를 선택 후 해당 주에 일정이 없으면, 일정이 표시되지 않는다.', async () => {
// 주 별 뷰 데이터 모킹
setupMockHandlerWeeklyView();
const { user } = setup(<App />);
// 목 데이터 있는지 확인
await screen.findAllByText(/다른 주 일정/);
// 월 별 뷰 선택
const comboboxes = screen.getAllByRole('combobox');
const monthCombobox = comboboxes.find((combobox) => combobox.textContent === 'Month');
expect(monthCombobox).toBeInTheDocument();
await user.click(monthCombobox!);
// 주 별 뷰 선택
await screen.findAllByText(/week/i);
await user.click(screen.getByRole('option', { name: /week/i }));
// 일정이 사라졌는지 확인
expect(screen.queryByText(/다른 주 일정/)).not.toBeInTheDocument();
});
그런데 이 코드는 App을 렌더링 하고있고, 프로바이더가 감쌌다고 하더라도 portal부분까진 아니라고 생각이 드는데, 어떻게 App 외부의 option을 클릭할 수 있었는지 의문입니다..! 혹시 프로바이더에서 portal이 포함되었을까요..?
질문 2. 유닛 테스트를 만들다 보니, 다른 케이스지만 내부적으로 같은 동작을 하는 경우가 있었습니다.
it('월의 경계에 있는 이벤트를 올바르게 필터링한다', () => {
const result = getFilteredEvents(EVENTS, '', new Date('2025-07-01'), 'month');
expect(result).toEqual([EVENTS[1], EVENTS[2], EVENTS[3]]);
});
it('월간 뷰에서 2025년 7월의 모든 이벤트를 반환한다', () => {
const result = getFilteredEvents(EVENTS, '', new Date('2025-07-01'), 'month');
expect(result).toEqual([EVENTS[1], EVENTS[2], EVENTS[3]]);
});
두 케이스는 월의 경계인 1일의 월간뷰를 보는 테스트와 7월의 월간뷰를 보는 테스트인데 사실상 내부적 동작이 같습니다. 그러면 이런 경우 하나의 설명으로 요약하고 테스트를 줄여야하는지, 아니면 월의 경계를 더 다양하게 설정해서 테스트를 했어야 하는지 궁금합니다.
질문 3. 통합테스트를 진행하면서 모든 테스트 케이스가 실제 유저가 동작시킬 수 있는 케이스 이어야 하는지 궁금합니다. 위에서도 한 번 설명했었는데, 예를들어 notification 시간이 다가와서 알림이 뜨는걸 확인하는 케이스가 있습니다. 그렇다면 이것을 가장 쉽게는 처음부터 알림시간에 도달해있는 아이템을 만들고 곧바로 알림이 뜨는걸 확인하는 방법과, 알림시간에 도달할 때 까지 시간을 흘려보낸 뒤 확인하는 방법, 마지막으로는 알림 생성부터 확인까지 모두 진행하는 방법 등 대략 세 가지 케이스로 진행할 수 있었습니다.
그렇다면 위 세 가지 중 어느 방식이 제일 통합테스트에 적절한가? 하는 의문이 듭니다. 사실 가장 마지막 방법은 앞서 추가 테스트를 진행했는데 굳이 또 해야하나? 하는 의문이 들고, 가장 첫 번째 방법은 그냥 알림이 뜨기만 할거면 hook에서 테스트한거랑 뭐가 다르지? 하는 의문이 듭니다..
과제 피드백
수고했습니다. 이번 과제는 테스트를 하는 다양한 방법들을 접하면서 테스트 코드를 작성한다라는 그 심리적 장벽을 허물고 친숙해지는데 있었습니다. 회고를 보아하니 잘 수행해준 것 같아요.
"처음 easy 난이도의 테스트들을 작성할 때, 생각보다 괜찮은데? 라는 생각으로 쉽게 작성했다.." 다행입니다. 매번 기수때마다 이번 챕터의 과제의 양에 압박을 받고 테스트 코드라는 안해본 걸 해야하기에 쉽게 시작을 못 하는 경우가 많았는데 순수 함수부터 시작해서 점진적으로 복잡도를 높여가는 과정이 테스트 코드의 허들을 낮추며 친해지는 계기가 되었으면 좋겠네요.
"마치 내가 QA를 하는것처럼 이런 케이스는 이렇게 검증해야지 하는 생각을 먼저 한 뒤 코드로 옮기면 쉽게 끝낼 수 있었다" 아주 테스트 코드는 첫번째로는 나 대신 컴퓨터가 구현한 결과를 디버깅한 결과를 테스트 하는 과정을 옮겨내는 것이고, 이후 사용자가 이 과정을 테스트할때 잘못된 행동을 하는 것을 추가적으로 검증하는 거니까요. 이 두가지를 생각하면 테스트 코드를 어떻게 작성해야할지 더 감이 잘 올거에요!
Q1) MUI의 Select컴포넌트의 option은 react portal로 열리는것을 확인했습니다. 그런데 어떻게 App 외부의 option을 클릭할 수 있었는지 의문입니다.
=> React Testing Library는 실제 DOM을 기반으로 동작하기 때문에 portal로 렌더링된 요소들도 찾을 수 있어요. 이건 Virtual DOM의 장점인데 결국 가상으로 만들어진 데이터이기에 React의 요소를 그대로 찾을 수 있는 것이죠. testing-library/react의 render 함수가 이러한 Virdual DOM document를 기준으로 쿼리를 수행을 하지요. 그래서 portal로 body에 추가된 요소들도 screen으로 접근 가능합니다.
Q2) 두 케이스는 사실상 내부적 동작이 같습니다. 이런 경우 하나의 설명으로 요약하고 테스트를 줄여야하는지, 아니면 월의 경계를 더 다양하게 설정해서 테스트를 했어야 하는지 궁금합니다.
=> 결과적으로 그렇게 만들어졌겠지만 테스트 케이스의 의도가 달라지면 결국은 테스트 코드가 달라지기 마련입니다. 애초에 "월의 경계에 있는 이벤트 필터링"과 "7월 전체 이벤트 반환"은 검증하고자 하는 시나리오가 다르기에 월의 경계에 있는 이벤트가 7월이 아닌 것들도 나중에는 검증을 하게 될테니까요. 물론 휘린이 말한대로 어색해 보이는 테스트 코드에 케이스의 의도를 더 살린 코드들을 추가해주면 더 좋겠죠!
Q3) 통합테스트에서 notification 알림 테스트 방법에 대한 질문입니다.
=> 두 번째 방법(알림시간까지 시간을 흘려보낸 뒤 확인)이 가장 적절합니다. 통합테스트의 목적은 여러 모듈이 함께 동작하는 것을 검증하는 거거든요. 시간 흐름 → 알림 발생이라는 전체 플로우를 확인하는 것이 의미가 있어요.
=> 첫 번째 방법은 휘린님 말씀대로 단위 테스트와 차이가 없고, 세 번째 방법은 너무 많은 것을 한 번에 검증해서 실패했을 때 원인 파악이 어려워요. 통합테스트는 초기 상태를 기반으로 요구사항의 의도를 하나씩 검증하는 것을 목적으로 합니다. 하나의 거대한 시나리오를 전체로 테스트를 하는건 e2e가 하는것이죠. 둘의 테스트가 추구하는 것이 다르므로 통합 테스트는 기능에 대한 요구사항의 케이스 검증이고 e2e는 사용자 관점에서 실제 환경에서의 하나의 큰 흐름을 테스트하는거라고 생각해주세요! :)
테스트 코드에 대한 전반적인 감을 잘 익히신 것 같아서 좋네요. 이런 고민들을 하면서 점점 더 나은 테스트를 작성하게 될 거예요. 다음 주 과제도 화이팅입니다