[7팀 강병준] Chapter 3-1. 프런트엔드 테스트 코드 #65
[7팀 강병준] Chapter 3-1. 프런트엔드 테스트 코드 #65BangDori wants to merge 31 commits intohanghae-plus:mediumfrom
Conversation
falsy / truthy 테스트를 toBe로 명확하게 검증하도록 변경
|
병준님의 PR보고 배워갑니다. 특히 toBe vs toBeTruthy/toBeFalsy를 읽고 toBeTruthy/toBeFalsy로 작성한 부분들이 정확하게 검증이 안 될 수 있다는 걸 알고 있던 부분들 호다닥 고쳤습니다. 감사합니다.^^ |
|
이번 과제도 열심히 하느라 고생하셨습니다! 그리고 질문이 있습니다!
|
|
이번 과제에서 깊게 고민해보지 못한 것들에 대한 질문을 주셔서 너무 감사합니다~~!
항해를 진행해오면서 저만의 클린 코드 기준이 생겼는데요, 제가 생각하는 클린 코드는 "의도를 잘 파악할 수 있는 코드"라고 생각해요. 이 말을 조금 풀어보면 다음과 같은 요소들을 포함한다고 생각합니다!
기타 등등.... 그리고 이걸 테스트 코드와 연관지어 생각해본다면, 이 기준은 테스트 코드에서도 동일하게 적용돼야 한다고 생각해요. describe('BankAccount', () => {
it('잔액이 0원일 때 10,000원을 입금하면 잔액이 10,000원이 된다', () => {
// Given: 초기 잔액이 0원인 계좌
const account = new BankAccount(0);
// When: 10,000원을 입금했을 때
account.deposit(10_000);
// Then: 잔액은 10,000원이 된다
expect(account.getBalance()).toBe(10_000);
});
});위 테스트 코드는 given-when-then 패턴을 사용해 가독성을 높이고자 했고, 이런 관점에서 보면, 테스트 코드 역시 실제 코드와 동일하게 클린 코드 원칙을 지켜야 한다고 생각합니다. 그래야 테스트 자체가 신뢰할 수 있는 문서이자 사양으로서의 역할을 충실히 할 수 있기 때문입니다.
제가 생각하는 각 테스트들은 다음과 같아요. 단위 테스트최소 단위의 모듈(함수, 공통 컴포넌트 등)을 검증하기 위한 테스트로서 개별 모듈이 정상적으로 동작하는지를 테스트하기 위한 것! 단위 테스트는 단순히 함수의 동작을 검증하기 위한 방법뿐만 아니라 코드의 품질을 올려주기 위한 방법도 될 수 있다고 생각해요. 그 이유는 함수의 동작을 Given-When-Then 패턴으로 테스트를 작성하게 되면, 본인이 만드려는 함수가 무엇인지 조금 더 명확하게 인지할 수 있고 또 이에 맞춰 코드를 작성하게 될 것이니까 가독성이 높아지지 않을까요?! (제 생각입니닷) 그리고 또 공통 모듈에서 정말 엄청난 장점을 가질 수 있다고 생각해요. 예를 들어 사내에서 사용하는 공통 모듈을 라이브러리화해서 패키지로 추출했다고 생각을 해볼게요. 여기서 패키지에 작성되어 있는 각 함수들에 대한 단위 테스트가 작성되어 있다면, 이 라이브러리를 사용하는 개발자들은 각 함수의 동작을 빠르게 파악할 수 있고 또 신뢰할 수 있지 않을까요? 그리고 이러한 내용을 정말 잘 보여주는 대표적인 라이브러리가 es-toolkit이라고 생각들어요. 통합 테스트여러 개의 모듈(컴포넌트, 서비스, 훅 등)이 서로 상호작용하는 과정을 검증하기 위한 테스트예요. 단일 함수가 올바르게 동작하는지 보는 게 아니라, 이벤트 → 여러 모듈 호출 → 결과라는 흐름이 끊김 없이 잘 이어지는지를 확인하는 거죠. 즉, 통합 테스트는 조금 더 실제 사용자 시나리오에 가까운 흐름을 보장한다고 볼 수 있어요. 예를 들어, "검색어를 입력했을 때 검색 결과가 화면에 노출된다."라는 일련의 과정을 하나로 묶어 검증할 수 있어요. 단위 테스트가 모듈 자체의 품질을 보장했다면, 통합 테스트는 모듈들이 합쳐졌을 때 애플리케이션이 의도대로, 시나리오대로 동작하는지를 보장하는 셈이죠. 물론! trade-off도 있겠죵?
설정이 조금 복잡하다는 근데, 완전 상황에 따라 다르게 해석될 수 있는데 오히려 Provider나 CSS 설정을 해주기 때문에 예측하지 못한 상황도 검증할 수 있다고 생각해요. E2E 테스트E2E(End-to-End) 테스트는 말 그대로 사용자 관점에서 서비스의 전체 흐름(워크플로우)을 끝까지 검증하는 테스트예요. 단위 테스트가 “모듈을 신뢰할 수 있는가?”, 통합 테스트가 “모듈들이 서로 잘 맞물리는가?”를 본다면, E2E는 **“사용자가 실제로 이 제품을 써서 원하는 가치를 얻을 수 있는가?”**에 집중하는 거죠. 예를 들어, 로그인 → 일정 생성 → 알림 설정 → 로그아웃 같은 저니맵 전체를 따라가는 시나리오를 테스트하는 게 대표적이에요. 이 단계에서 문제가 생기면 바로 돈과 직결되는 부분이라, 흔히 "서비스에서 가장 중요한 테스트"라고 불리기도 하죠. 또한 E2E 테스트는 실제 환경과 매우 유사하게 동작하기 때문에, 네트워크 지연, 브라우저 호환성, API 응답 속도 같은 외부 요인에 대해서도 현실적인 피드백을 줍니다. 그래서 단위/통합 테스트에서 발견하지 못한 문제를 확인할 수 있다는 장점이 있어요. 하지만! 가장 느리고 실패했을 때 원인 파악이 어렵다는 단점이 있어요..! 그럼에도 불구하고 E2E 테스트는 "사용자가 우리 서비스를 믿고 사용할 수 있는지"를 나타낼 수 있는 중요한 지표라고 생각을 해요. 그렇기 떄문에 굉~장히 중요하지 않나~~ 여담으로, 제가 예전 기술 면접에서 면접관분들한테 "E2E 테스트를 하면 신뢰성을 보장할 수는 있지만 실제 환경과 유사하게 가져가기 위해서는 외부 의존성이 발생하게 되는데 이로 인한 리소스가 너무 많이 들지는 않나요?" 라는 질문을 한 적이 있어요. 당시에는 E2E 테스트에 대한 큰 이해가 없었던 상황이라, '리소스 관리가 너무 힘들지 않나?'라고만 생각했었는데 면접관분의 답변을 듣고나서 굉장히 좁은 식견으로 테스트를 바라보고 있었구나.. 라는 생각을 했습니다.. ㅎㅎㅎㅎ "외부 의존성이 많이 생겨 리소스가 많이 들기는 하지만 오히려 이런 의존성을 그대로 다 받아내기 때문에 실제 환경과 더 유사하게 동작하게끔 할 수 있고, 문제로 인한 실패도 파악할 수 있어 사용자에게 더 높은 품질의 애플리케이션을 제공할 수 있어서 우리 팀은 E2E 테스트를 작성한다."라고 답변을 해주셨어요. 이런 경험을 통해 E2E 테스트는 단순히 비용이 큰 게 아니라, 품질을 보장하는 중요한 투자라는 걸 배웠어요. |
Medium
7주차 과제 체크포인트
기본과제
Medium
질문
toastFn과 mock은 외부 라이브러리인 notistack의 토스트 알림 기능을 테스트하기 위한 핵심 요소입니다.
enqueueSnackbarFn은 Vitest의 모킹 함수로, 실제 토스트 알림을 표시하는 enqueueSnackbar 함수를 대체합니다. 이를 통해 테스트 환경에서 실제 UI 토스트를 렌더링하지 않고도 함수가 올바른 메시지와 옵션으로 호출되었는지 검증할 수 있습니다.
vi.mock('notistack')은 notistack 라이브러리 전체를 모킹하되, useSnackbar 훅만 가짜 구현으로 교체합니다. 이때 실제 라이브러리의 다른 기능들은...actual을 통해 그대로 유지하고, useSnackbar만 테스트용 함수를 반환하도록 오버라이드합니다.이러한 모킹 전략을 통해 useEventOperations 훅의 각종 작업(이벤트 생성, 수정, 삭제) 후 적절한 성공/실패 메시지가 토스트로 표시되는지 단위 테스트에서 검증할 수 있으며, 외부 의존성 없이 빠르고 안정적인 테스트 실행이 가능합니다.
각 테스트는 해당 작업이 완료된 후
expect(enqueueSnackbarFn).toHaveBeenCalledWith(...)를 통해 올바른 메시지가 전달되었는지 확인합니다.Provider를 상위에 Wrapping 해줌으로써 통합 테스트의 측면에서 조금 더 넓은 범위의 상황을 고려할 수 있게 됩니다.
이렇게 작성되어 있는 setup을 사용함으로써 테스트에서 (1) 스타일링 관련 이슈, (2) 테마 의존적인 동작 등을 할 수 있어 실제 사용자 경험에 가까운 테스트를 수행할 수 있습니다.
handlersUtils에 정의된 함수들은 MSW(Mock Service Worker)를 사용한 API 모킹 핸들러들로, 테스트 환경에서 실제 백엔드 API 없이도 프론트엔드 기능을 테스트할 수 있게 해주는 핵심적인 도구입니다.
use를 사용하게 되면 실제 api 요청에 대해 서비스 워커가 이를 가로채어 응답을 반환하게 됩니다. 방법 자체는 간단하고, 특정 api를 호출해야하는 경우 해당 api에 대한 응답을 모킹하고자 할 때 활용될 수 있습니다.
테스트 환경의 일관성과 예측 가능성을 보장하기 위한 핵심적인 설정..이 아닐까요?
시간은 고정적이지 않고 항상 동적인 상태를 띄고 있기 때문에 특정 시간대에 의존하게 되면 시간이 지남에 따라 테스트의 신뢰성과 결정성이 깨지는 위험이 있습니다. 그렇기 때문에 이를 해소하고자 특정 시간대를 fake로 설정하고 사용하는 것이 아닐까요?
심화 과제
과제 셀프회고
기술적 성장
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만 검증단위 / 통합 / E2E 차이
빠르고 안정적이며 버그 위치를 파악하는데 매우 용이합니다! 다만 단일 모듈에 대한 검증이 주로 이뤄지기 때문에 단위 테스트가 통과한다고 안정적인 애플리케이션이라는 것을 보장할 수는 없습니다. => 즉 코드 퀄리티와 관련한 것으로 봐도 무방할 듯
검색 -> UI 업데이트까지의 사용자 시나리오에 대한 테스트를 수행할 수 있기 떄문에 여러 모듈을 한 번에 테스트할 수 있음. 하지만 단위 테스트보다 느리고 설정이 생각보다 복잡하다. 컴포넌트 연동 간 적절하지 않은 모킹을 하는 경우 테스트의 신뢰성이 크게 저하할 수 있음.
로그인부터 일정 생성, 알림 설정, 로그아웃까지 저니맵을 기준으로 제품의 가치 전반을 확인할 수 있는 테스트. 거의 실제 환경과 유사하므로 테스트시 외부 요인(네트워크 환경이 안좋은 등)에 대해서도 잘 반응해서 단위/통합 테스트에서 확인하지 못한 문제를 조기에 확인할 수 있음. 하지만 가장 느리며 환경에 의존적이라는 문제가 있다.
그래서 내가 생각할 때의 기준은,
코드 품질
없습니다!
학습 효과 분석
기존에는 공휴일 테스트를 작성할 때, 테스트 실행 결과로만 어떤 공휴일이 포함되는지 확인할 수 없었습니다.
하지만 이런 방식은 테스트만 봐서는 어떤 공휴일이 있는지 직관적으로 알기 어렵다는 한계가 있었습니다. 이를 개선하기 위해 holidayNames 필드를 추가하고, 테스트 이름에서 바로 공휴일 이름을 드러나도록 변경했습니다.
이렇게 변경하면서 테스트 자체가 실행 검증뿐만 아니라, 문서로서도 어떤 달에 어떤 공휴일이 있는지를 한눈에 보여주는 역할을 하게 되었습니다.
이 과정에서 테스트 코드의 description을 잘 작성하는 것도 디버깅 관점에서나 문서화의 관점에서 굉장히 중요하구나! 라는 사실을 배우게 되었습니다.
과제 피드백
이전까지는 테스트를 작성할 때 있어 크게 '이 테스트가 가진 의도는 무엇인지', '이 테스트가 필요한 테스트인지'를 생각하지 못했던 것 같습니다. 하지만 이번 과제에서 의도가 분명하지 않거나 중복된 의도를 가진 테스트들을 찾아나가는 과정이 생각보다 재미있었고 저런 고민들이 왜 필요한지를 배울 수 있었던 것 같습니다.
리뷰 받고 싶은 내용