Medium
7주차 과제 체크포인트
기본과제
Medium
- 총 11개의 파일, 115개의 단위 테스트를 무사히 작성하고 통과시킨다.
질문
Q. medium.useEventOperations.spec.tsx > 아래 toastFn과 mock과 이 fn은 무엇을 해줄까요?
toastFn과 mock은 외부 라이브러리인 notistack의 토스트 알림 기능을 테스트하기 위한 핵심 요소입니다.
enqueueSnackbarFn은 Vitest의 모킹 함수로, 실제 토스트 알림을 표시하는 enqueueSnackbar 함수를 대체합니다. 이를 통해 테스트 환경에서 실제 UI 토스트를 렌더링하지 않고도 함수가 올바른 메시지와 옵션으로 호출되었는지 검증할 수 있습니다.
vi.mock('notistack')은 notistack 라이브러리 전체를 모킹하되, useSnackbar 훅만 가짜 구현으로 교체합니다. 이때 실제 라이브러리의 다른 기능들은 ...actual을 통해 그대로 유지하고, useSnackbar만 테스트용 함수를 반환하도록 오버라이드합니다.
이러한 모킹 전략을 통해 useEventOperations 훅의 각종 작업(이벤트 생성, 수정, 삭제) 후 적절한 성공/실패 메시지가 토스트로 표시되는지 단위 테스트에서 검증할 수 있으며, 외부 의존성 없이 빠르고 안정적인 테스트 실행이 가능합니다.
각 테스트는 해당 작업이 완료된 후 expect(enqueueSnackbarFn).toHaveBeenCalledWith(...)를 통해 올바른 메시지가 전달되었는지 확인합니다.
Q. medium.integration.spec.tsx > 여기서 Provider로 묶어주는 동작은 의미있을까요? 있다면 어떤 의미일까요?
Provider를 상위에 Wrapping 해줌으로써 통합 테스트의 측면에서 조금 더 넓은 범위의 상황을 고려할 수 있게 됩니다.
const setup = (element: ReactElement) => {
const theme = createTheme();
const user = userEvent.setup();
return {
...render(
<ThemeProvider theme={theme}>
<CssBaseline />
<SnackbarProvider>{element}</SnackbarProvider>
</ThemeProvider>
),
user,
};
};
이렇게 작성되어 있는 setup을 사용함으로써 테스트에서 (1) 스타일링 관련 이슈, (2) 테마 의존적인 동작 등을 할 수 있어 실제 사용자 경험에 가까운 테스트를 수행할 수 있습니다.
Q. handlersUtils > 아래 여러가지 use 함수는 어떤 역할을 할까요? 어떻게 사용될 수 있을까요?
handlersUtils에 정의된 함수들은 MSW(Mock Service Worker)를 사용한 API 모킹 핸들러들로, 테스트 환경에서 실제 백엔드 API 없이도 프론트엔드 기능을 테스트할 수 있게 해주는 핵심적인 도구입니다.
use를 사용하게 되면 실제 api 요청에 대해 서비스 워커가 이를 가로채어 응답을 반환하게 됩니다. 방법 자체는 간단하고, 특정 api를 호출해야하는 경우 해당 api에 대한 응답을 모킹하고자 할 때 활용될 수 있습니다.
Q. setupTests.ts > 왜 이 시간을 설정해주는 걸까요?
테스트 환경의 일관성과 예측 가능성을 보장하기 위한 핵심적인 설정..이 아닐까요?
시간은 고정적이지 않고 항상 동적인 상태를 띄고 있기 때문에 특정 시간대에 의존하게 되면 시간이 지남에 따라 테스트의 신뢰성과 결정성이 깨지는 위험이 있습니다. 그렇기 때문에 이를 해소하고자 특정 시간대를 fake로 설정하고 사용하는 것이 아닐까요?
심화 과제
- App 컴포넌트 적절한 단위의 컴포넌트, 훅, 유틸 함수로 분리했는가?
- 해당 모듈들에 대한 적절한 테스트를 5개 이상 작성했는가?
과제 셀프회고
기술적 성장
toBe vs toBeTruthy/toBeFalsy
기존에는 boolean 값 검증 시 toBeTruthy()나 toBeFalsy()와 toBe()의 차이를 몰랐지만, 이제는 정확한 타입과 값을 검증하는 toBe(true/false)의 중요성을 이해하게 되었습니다.
toBeTruthy(): 1, "hello", [] 등 truthy한 모든 값을 통과시킴toBeFalsy(): 0, "", null, undefined 등 falsy한 모든 값을 통과시킴toBe(true/false): 정확히 boolean 타입의 true/false만 검증
// 개선 전: 의도하지 않은 값도 통과할 수 있음
expect(isValidInput("")).toBeFalsy(); // 빈 문자열도 통과
// 개선 후: 정확한 boolean 값만 검증
expect(isValidInput("")).toBe(false); // 명확한 false 값만 통과
단위 / 통합 / E2E 차이
- 단위 테스트: 개별 함수나, 컴포넌트, 훅 등 하나의 단일 모듈에 대한 검증 (이 모듈을 신뢰할 수 있는가?)
빠르고 안정적이며 버그 위치를 파악하는데 매우 용이합니다! 다만 단일 모듈에 대한 검증이 주로 이뤄지기 때문에 단위 테스트가 통과한다고 안정적인 애플리케이션이라는 것을 보장할 수는 없습니다. => 즉 코드 퀄리티와 관련한 것으로 봐도 무방할 듯
- 통합 테스트: 여러 컴포넌트나 모듈 간의 상호작용을 검증 (어떤 이벤트에 대해 신뢰할 수 있는가?)
검색 -> UI 업데이트까지의 사용자 시나리오에 대한 테스트를 수행할 수 있기 떄문에 여러 모듈을 한 번에 테스트할 수 있음. 하지만 단위 테스트보다 느리고 설정이 생각보다 복잡하다. 컴포넌트 연동 간 적절하지 않은 모킹을 하는 경우 테스트의 신뢰성이 크게 저하할 수 있음.
- E2E 테스트: 사용자 관점에서의 전체 워크플로우를 검증 (돈이 되는 거. 서비스에서 가장 중요한 거)
로그인부터 일정 생성, 알림 설정, 로그아웃까지 저니맵을 기준으로 제품의 가치 전반을 확인할 수 있는 테스트. 거의 실제 환경과 유사하므로 테스트시 외부 요인(네트워크 환경이 안좋은 등)에 대해서도 잘 반응해서 단위/통합 테스트에서 확인하지 못한 문제를 조기에 확인할 수 있음. 하지만 가장 느리며 환경에 의존적이라는 문제가 있다.
그래서 내가 생각할 때의 기준은,
- 빠른 피드백 사이클 혹은 코드 퀄리티 라면 단위 테스트
- 여러 모듈 간 연동성 혹은 사용자 시나리오를 테스트하기 위함이라면 통합 테스트
- 제품에서 가장 중요한 기능이다라면 E2E 테스트
- 돈을 벌어다 주는 기능이다? E2E 테스트
- 해당 기능이 잘못 동작하면 불침번을 서야한다? E2E 테스트
코드 품질
없습니다!
학습 효과 분석
기존에는 공휴일 테스트를 작성할 때, 테스트 실행 결과로만 어떤 공휴일이 포함되는지 확인할 수 없었습니다.
it.each([
{ month: 3, expected: { '2025-03-01': '삼일절' } },
{ month: 5, expected: { '2025-05-05': '어린이날' } },
// ...
])('$month월에는 공휴일이 있다', ({ month, expected }) => {
const result = fetchHolidays(new Date(`2025-${month}`));
expect(result).toEqual(expected);
});
하지만 이런 방식은 테스트만 봐서는 어떤 공휴일이 있는지 직관적으로 알기 어렵다는 한계가 있었습니다. 이를 개선하기 위해 holidayNames 필드를 추가하고, 테스트 이름에서 바로 공휴일 이름을 드러나도록 변경했습니다.
it.each([
{ month: 3, expected: { '2025-03-01': '삼일절' }, holidayNames: '삼일절' },
{ month: 5, expected: { '2025-05-05': '어린이날' }, holidayNames: '어린이날' },
// ...
])('$month월에는 $holidayNames 공휴일이 있다', ({ month, expected }) => {
const result = fetchHolidays(new Date(`2025-${month}`));
expect(result).toEqual(expected);
});
이렇게 변경하면서 테스트 자체가 실행 검증뿐만 아니라, 문서로서도 어떤 달에 어떤 공휴일이 있는지를 한눈에 보여주는 역할을 하게 되었습니다.
이 과정에서 테스트 코드의 description을 잘 작성하는 것도 디버깅 관점에서나 문서화의 관점에서 굉장히 중요하구나! 라는 사실을 배우게 되었습니다.
과제 피드백
이전까지는 테스트를 작성할 때 있어 크게 '이 테스트가 가진 의도는 무엇인지', '이 테스트가 필요한 테스트인지'를 생각하지 못했던 것 같습니다. 하지만 이번 과제에서 의도가 분명하지 않거나 중복된 의도를 가진 테스트들을 찾아나가는 과정이 생각보다 재미있었고 저런 고민들이 왜 필요한지를 배울 수 있었던 것 같습니다.
리뷰 받고 싶은 내용
- 테스트 코드의 리팩토링은 언제, 어떻게 진행하는 게 좋을까요?
- 테스트 코드 작성 능력을 향상시키기 위해 어떤 학습 방법을 추천하시나요? (방식보다는 어떤 관점을 가지고 접근해야할 지 궁금합니다. ex. 이 테스트의 의도는 무엇일지 생각해보기 등등등...)
과제 피드백
고생하셨습니다! 양이 꽤 많았는데 테스트를 작성하시면서 많이 익숙해지신것 같아서 좋네요 ㅎㅎㅎ 작성해주신 내용이나 회고를 읽어보니 알차게 한 주를 보내셨었던 것 같네요. 결국 테스트가 공부를 할수록 명확한 기준이 생기기 전까지는 모호한 기준이지 않을까, 어떤 부분을 검증할까 라는 것들이 바로 기준이 생기시지 않을 수 있지만 결국 많이 작성해보고 운영해보는 경험들이 도움을 줄 것이라고 생각합니다. 여유가 있으시다면 하드도 한번 도전해서 해결해보시면 좋겠어요.
테스트 코드의 리팩토링은 언제, 어떻게 진행하는 게 좋을까요?
사실 테스트 코드는 작성하는 시점에 깔끔하게 작성하는게 좋지만, (사실 그렇게 복잡한 로직은 없을 것이라는 가정이기 때문에) 작성한 직후 테스트가 통과한다면 정리를 하고 넘어가는게 좋은 것 같아요 ㅎㅎ 만약 추가 어설션을 선언해서 쓰는 것처럼 전체적 개선이 필요하다면 말 그대로 '필요해질 때' 쓰는게 좋은 것 같습니다. 말 그대로 테스트는 검증의 기준이기 때문에 자주 변한다는 것 자체가 (리팩토링의 맥락에서는 영향이 없어야 하지만) 좋은 신호는 아닌것 같아요!
테스트 코드 작성 능력을 향상시키기 위해 어떤 학습 방법을 추천하시나요?
작성해주신것처럼 테스트의 의도를 파악하는게 가장 중요한것 같아요. 지금은 제가 테스트를 작성해드렸지만 결국 무엇을 테스트할지, 범위를 어떻게 정해야할지 생각해야하는건 병준님이시니까요. 어떻게 케이스를 정의해야할지 모르겠다면 가장 좋은 방법은 명세를 정리해보는겁니다. 기획서를 직접 정의하지 않았다면 정리를 하고 테스트 할만한 시나리오들을 추려보는거죠. 유저 관점에서 사용하는데 중요한 시나리오들에 맞춰 데이터를 이전 Q&A때 공유드렸던 방법론들처럼 나눠보는것도 방법일 것 같아요. (아니면 AI에게 켄트벡의 페르소나를 주입해 테스트 케이스를 만드는것도 큰 도움을 받는 방법일 수도..!)
고생하셨고 담주도 화이팅입니다!