프론트엔드 테스트 입문기
프론트엔드 테스트에 처음 입문한 과정 기록
소프트웨어 아키텍처
입문하게 된 이유
나를 위해
사실 프론트엔드 테스트에 관해서는 과제의 요구사항에 없었다.
그럼에도 프론트엔드 테스트를 공부하려고 하는 것은 나의 코드 작성 능력을 기를 수 있는 하나의 수단이라고 생각하기 때문이다.
내가 작성한 코드가 의도대로 작동하는지를 즉시 확인할 수 있고, 리팩토링을 했을 때 기존 기능이 망가지지 않았음을 테스트를 통해 자동으로 보장받을 수 있다.
또한 나중에 코드를 다시 볼 때 까먹을 일 없이 해당 코드가 ‘어떻게’ 사용되어야 하고, ‘어떤 결과’를 반환해야 하는지를 명확하게 보여주는 일종의 명세서 역할을 한다.
이는 나의 주 분야인 프론트엔드 테스트에서 특히 잘 드러나는 강점이라고 생각한다. 눈으로 보이는 것과 내부 동작을 동시에 만들어야 하는 프론트엔드 특성 상 관리할 코드가 많고 복잡하게 얽힐 수 있으며, 그러다보니 개발하면서 나 조차도 관리하기 힘들어지는 일을 자주 겪기 때문이다.
팀 활동을 위해
학습 스프린트 과정이 끝나면 한 팀으로 장기간 프로젝트를 하게 된다.
특히 혼자 개발하는 것이 아닌 여러 사람들과 함께하는 것이기에 항상 동일한 조건에서 개발되어야 하고, 체계적이고 짜임새 있는 설계를 통해 유지보수하기 쉬운 코드를 만드는 것이 나의 목표가 되었다.
테스트를 적용하면 개발 초기에 결함을 식별하여 수정할 수 있어 나중에 버그를 수정할 때보다 훨씬 비용이 저렴하다고 한다. 또한 각 팀원이 작성한 모듈이 서로 통합될 때 예상대로 작동하는지 테스트를 통해 확인하고, 이를 자동화해서 효율을 개선할 수 있다.
테스트 입문
“왜” 테스트를 하는지, 그리고 테스트의 “무엇”을 알아야 하는지 명확히 할 것
테스트를 제대로 하는 것이 처음이기 때문에 테스트의 종류와 테스트 피라미드에 대해서 학습했다.
단위, 통합, E2E 이렇게 세 가지 테스트가 있고, 그 중에서 단위 테스트가 다른 테스트보다 비교적 우선 순위가 높다는 걸 알 수 있었다.
각 테스트 종류의 목적을 간단하게 파악하고 나니 그동안 막연하게 다가왔던 테스트 개념들이 조금씩 뚜렷하게 보이기 시작했다.
테스트 코드는 어떻게 만들어야 하나?
테스트는 단순히 생각만 해서 되는 게 아니라, 테스트 코드를 짜야 한다.
그러나 나는 프레임워크의 제대로 된 사용법도 모를 뿐더러 뭘 테스트해야 하는지도 잘 알지 못한다.
따라서 AI를 아주 적극적으로 활용해서 테스트 코드와 시나리오, 테스트 케이스를 만들었다.
다만 맞는 방법인가 고민이긴 하다. 할루시네이션 때문에 기능의 요구사항과 전혀 다른 테스트 케이스를 AI가 생성하는 경우가 빈번하여 추가적인 검증이 필요했다.
프론트엔드 테스트로 첫 시작
처음은 내가 좋아하는 프론트엔드로 시작한다.
백엔드 테스트는 AI를 통해 경험해본 적이 있는데, 프론트엔드는 과연 어떻게 테스트를 하는지 정말 궁금했다.
또 프론트엔드는 과연 테스트가 개발에 얼마나 중요한 영향을 미칠지도 관심사였다.
핵심 문제
프론트엔드를 개발하다 보면 이런 현상이나 걱정을 자주 겪는다.
“CSS 한 줄 고쳤는데 전체 레이아웃이 틀어졌다!” “배포하고 나서 내 기능이 제대로 작동하지 않는다!!”
이런 걸 **’회귀 버그’**라고 한다고 한다. 예전에는 작동했지만 지금은 작동하지 않는 무언가, 일반적으로 새 코드를 추가할 때 발생하는 버그다.
어쩌면 프론트엔드 개발자의 가장 큰 적일 것이다.
이러한 불안감을 없애고, 보다 자신있게 개발 및 배포를 수행하기 위해 테스트를 적극적으로 사용해야 함을 깨달았다.
첫 번째 무기
Jest와 Mocha 말고는 아는 테스트 도구가 없었다.
그나마 들어본 게 있다면 Storybook 정도이다. 예전에 대외활동에서 다른 팀의 프로젝트를 구경할 때 Storybook UI를 아주 적극적으로 활용하는 걸 보고 꽤 감탄했다.
그래서 일단은 AI에게 프론트엔드 테스트를 학습하기 위한 도구들을 추천받았다.
Vite 위에서 개발을 하기 때문에 Vite에서 만든 테스팅 프레임워크인 Vitest, 리액트 컴포넌트 테스트의 표준처럼 여겨지는 React Testing Library(RTL)을 사용하게 되었다.
Vitest와 RTL의 핵심 역할을 이 글을 쓰면서 되돌아보면, Vitest는 테스트 실행기, RTL은 사용자 관점의 테스트 도구이다. 이 두 도구의 역할을 잘 살려 본격적으로 테스트를 시작하게 되었다.
순수 컴포넌트 단위 테스트로 입문
UI 컴포넌트가 props에 따라 올바르게 렌더링되는지를 확인하기 위한 단위 테스트로 첫 프론트엔드 테스트를 시작했다.
제일 간단한 컴포넌트에 대해 테스트를 한다면 위에 언급한 두 라이브러리를 활용한 테스트가 어떻게 돌아가는지 감을 잡을 수 있을 것이라 생각했다.
여기서 RTL의 핵심 철학을 다시 한 번 알 수 있었다. props나 state같은 개발자 관점에서의 구현 세부사항 보다는, 사용자 관점에서 눈에 보이는 것을 테스트하는 느낌이었다.
그래서 여기서는 props에 넣은 특정 CSS 스타일의 값이 잘 적용 되었는지 검증해봤다.
상호작용 기반 단위 테스트
컴포넌트는 단순히 보이는 역할만 하는 게 아니라 클릭, 호버, 타이핑 등 사용자의 행동에 따라 다양한 기능을 제공한다. 따라서 사용자의 행동에 따라 컴포넌트가 올바르게 반응하는지 검증하기 위한 단위 테스트도 할 수 있음을 배웠다.
여기서는 RTL을 사용하여 버튼 클릭을 시뮬레이션 했다. 클릭 후 의도한 결과는 잘 렌더링이 되었는지 검증했다.
또한 Vitest의 모킹 함수를 이용해 내가 개발했던 커스텀훅의 구현을 가짜 구현으로 대체해 API 서버의 상태와 관계없이 테스트를 진행할 수 있도록 했다.
API 연동 통합 테스트
단위 테스트도 해봤으니 통합 테스트도 해봐야겠다 생각이 들어 API와 연동이 되어있는 컴포넌트를 대상으로 통합 테스트를 진행했다.
실제 API를 호출하는 것은 시간이 걸리기 때문에 독립성을 보장하고자 Mock API를 호출하여 데이터를 받아와 화면에 잘 그리는지 검증하는 것을 목표로 했다.
공부를 목적으로 하는 테스트이기에 API 의존성이 명확하고, 테스트하기 쉽고, UI 검증이 명확한 컴포넌트를 골랐다.
명확한 한계
RTL과 Vitest를 사용해 단위/통합 테스트를 해보면서 테스트 코드 만으로 Happy Path 뿐만 아니라 Unhappy Path에 대해서도 명확하게 고려해볼 수 있어서 좋다고 느꼈다. 특히 사용자 관점에서 테스트하니 개발하면서 생각하지 못하는 부분에 대해서도 캐치할 수 있겠다라고 생각했다.
그러나 이것만으로는 부족한 점이 있었다.
기능은 동작하는데, 이게 예쁘게 보이는지 확인하는 것, 컴포넌트가 특정 상태에서 정확히 의도한 상태로 렌더링 되는지를 눈으로 확인하는 것, 컴포넌트 하나 보려고 앱 전체를 실행하는 것이 비효율적이라 느꼈다.
새로운 무기
그래서 여기서 Storybook을 도입하게 된다.
앱과 완전히 분리된 환경에서 컴포넌트를 하나씩 띄워볼 수 있다는 게 가장 큰 장점인 거 같다. 또한 상태에 따라 렌더링 결과물이 다른 컴포넌트에 대해서 일일이 내가 직접 코드를 작성해서 상태를 바꿔주지 않아도 Storybook에서는 즉시 확인할 수 있어서 개발 속도 향상의 이점도 가져갈 수 있을 거 같다.
내가 처음에 쓴 도구들이랑 뭐가 다를까?
처음에 RTL/Vitest, Storybook은 같은 프론트엔드 테스트 도구이면서 유저 관점에서 테스트한다는 점에서 거의 비슷하게 느껴졌다.
그래서 무엇이 다른지 생각해 봤다.
RTL와 Vitest는 컴포넌트의 로직과 기능을 눈에 보이지 않는 가상 DOM에서 실행하면서 테스트한다면, Storybook은 컴포넌트의 시각적 결과물을 실제 브라우저에서 눈으로 확인하며 개발하고 검토할 수 있다는 게 차이점인 거 같다.
즉, RTL와 Vitest는 기능 보장, Storybook은 시각/경험 보장 목적임을 알게 되었다.
아직은 사용하기 힘들다
AI에게 내가 단위/통합 테스트를 이미 짠 컴포넌트에 대해 스토리 코드를 만들어달라고 맡겼다.
근데 생각한 거 만큼 잘 나오지 않았다. 개발에 사용하고 있는 라이브러리(라우터, Emotion 등)와 충돌해 문제가 발생하기도 했다. 사용자 상호작용으로 상태가 자주 바뀌는 컴포넌트에 대해서는 실제 페이지 환경과 완전 다르게 작동해 제대로 테스트를 하는 건가 의문이 들었다.
그래서 Storybook은 사용할 거면 아예 프로젝트 초기에 도입해야 좋다는 것을 강하게 느꼈다.
Storybook에 기반해서 개발한다면 충동에 의해 설계를 벗어날 위험도 줄어들고, 컴포넌트 기반 아키텍처를 제공하는 리액트에서 개발하면서 컴포넌트를 보다 효율적, 체계적으로 만듦으로써 유지보수성을 높이는 결과로 이어질 것이라 생각한다.
그래서 이번에 얻은 진짜 가치는?
사실 내가 테스트를 제대로 공부한 거냐고 물어본다면 당연히 아니라고 느낀다.
이제 하루 이틀 한 거고, 테스트 코드와 시나리오, 케이스 생성을 전부 AI에게 맡기고 나는 검증하는 역할만 했다.
그럼에도 각종 도구들을 통해 테스트하는 것을 직접 내 눈으로 보고, 손으로 느끼며 프론트엔드 테스트에서 가져가는 이점들을 강하게 느낄 수 있었다.
어떤 이점들을 가져갈 수 있을까?
인간은 도구를 활용해야 한다. Vitest, RTL, Storybook 세 가지 도구들을 적절히 조합했을 때 엄청난 이점을 가져갈 수 있음을 느꼈고, AI와 함께 도구 조합의 이점들을 리뷰하며 마무리지었다.
시각적 안정성
- 문제: CSS 수정 후 UI가 깨지는 것 (회귀 버그)
- 해결:
- Storybook을 Chromatic과 같은 도구와 연동하면, 코드가 변경될 때마다 모든 컴포넌트의 스크린샷을 찍어 자동으로 비교
- Vitest는 컴포넌트가 렌더링하는 DOM 구조가 의도치 않게 변경되지 않았는지 스냅샷 테스트를 실행
- 결과:
- 1px이라도 다르면 CI가 실패하고, 내가 모르는 사이드 이펙트는 없다는 확신을 얻음
- Vitest가 코드 레벨의 구조 변경을 잡고, Storybook이 실제 렌더링 된 시각적 변경을 잡아내는 것
빠르고 집중된 개발
- 문제: UI 하나 고치려고 자식, 자식, 자식, 컴포넌트로 계속 파고든 다음에 서버 실행하고, 버튼 클릭하고, …
- 해결:
- 컴포넌트 주도 개발(CDD)이 가능해진다.
- Storybook에서 먼저 컴포넌트를 완벽하게 만든 후, 앱에 조립한다.
- RTL을 사용해 “클릭하면 함수가 호출된다”와 같은 핵심 로직을 테스트할 수 있다.
- 컴포넌트 주도 개발(CDD)이 가능해진다.
- 결과: API 연동이나 라우팅을 기다릴 필요 없이 UI와 로직 개발을 독립적으로, 빠르게 진행할 수 있다.
완벽한 커뮤니케이션
- 문제: 피그마와 실제 코드가 따로 노는 현상
- 해결:
- (시각적 문서) Storybook은 컴포넌트의 모든 상태를 보여주면서 모두가 참고할 수 있는 살아있는 디자인 시스템이 됨
- (기능적 문서) RTL로 작성된 코드는 기능 명세서 역할을 함
- 결과:
- 프로젝트에 참여하는 모두가 동일한 결과물을 보고 소통하여 불필요한 오해를 줄임
- 시각적 결과물과 기능적 요구사항이 동시에 나오면서 프로젝트 구성원 모두를 만족시킴
견고한 품질
- 문제: 테스트는 통과하는데, 스크린 리더 사용자에게는 어떻게 다가올까?
- 해결:
- Storybook의
addon-a11y가 자동으로 접근성 위반 사항을 검사 - Storybook의
play함수로 스토리+RTL 테스트까지 실행해 기능 테스트 - RTL을 사용해 컴포넌트의 핵심 기능을 사용자 관점에서 철저히 테스트 (품질의 핵심 기반)
- Storybook의
- 결과:
- Vitest + RTL로 견고한 로직을 보장하고, Storybook으로 시각적 무결성과 접근성까지 커버한다.
- 기능, 시각, 접근성까지 모두 커버하는 견고한 컴포넌트를 만들 수 있다.