「프론트엔드 테스트 제대로 하기」 시리즈의 22편입니다. 전체 목차 보기 · 용어집

작은 앱 하나에 테스트를 한 겹씩 쌓으면 무엇이 되는지 — 이 글은 그 여정의 회고이자, 프론트엔드 테스트 전체의 지도입니다.

쌓은 겹은 이런 것들이었어요. 함수 하나가 제 일을 하는지 확인하는 단위 테스트, 사용자처럼 화면을 눌러보는 컴포넌트 테스트, 진짜 브라우저를 열어 끝에서 끝까지 훑는 E2E, 화면을 보지 않는 사용자를 지키는 접근성 검사, 그리고 이 전부를 PR마다 대신 돌려주는 CI. 마지막에는 AI가 짠 코드와 테스트를 검증하는 이야기까지 왔고요.

시리즈를 완주한 분께는 결산이고, 이 글에 처음 도착한 분께는 목차입니다. 아래 표의 각 층이 곧 시리즈의 챕터니, 끌리는 층부터 목차로 들어가시면 돼요.

마지막 편답게 기술 너머의 이야기도 합니다 — 테스트가 팀 문화와 커리어에서 무엇인지까지.

실습 코드: frontend-testing-lab — 시리즈가 끝난 시점의 최종 상태입니다. 각 편이 끝난 코드는 README의 편↔태그 매핑표를 보고 git checkout step-N으로 열어보세요.

마지막 편에서 돌아볼 것들.

  • 우리가 쌓아온 것 한눈에 정리
  • 테스트가 만드는 팀 문화, 채용·경진대회 관점
  • 여기서 더 나아갈 길
0장부터 5장까지 쌓아 올린 테스트 층위를 하나의 그림으로 정리한 회고 다이어그램
0장부터 5장까지 쌓아 올린 테스트 층위를 하나의 그림으로 정리한 회고 다이어그램

우리가 만든 것 — 앱 하나에 쌓인 다섯 층

작은 대시보드 하나에 이만큼이 쌓였습니다. 표의 첫 세 줄은 내려갈수록 실제 사용자가 겪는 상황에 가까워지고, 나머지 둘은 그 전부를 가로지릅니다. 괄호 안 도구 이름은 지금 몰라도 괜찮아요 — 각 줄이 시리즈의 한 챕터라, 필요해질 때 그 편으로 가시면 됩니다.

데이터 표
무엇어디
단위필터·디바운스·로거 (AAA, fake timers, spy)src/lib/*.test.ts
컴포넌트검색·에러·접근성 흐름 (RTL + user-event + MSW)src/**/*.test.tsx
E2E검색 시나리오 + 픽스처 + 스크린샷 비교 (Playwright)e2e/*.spec.ts
접근성role 계약 + axe 페이지 스캔쿼리 계약·axe 편
자동화PR마다 도는 품질 게이트.github/workflows/test.yml

숫자로 보면 이렇습니다. 테스트 파일 8개에 테스트 18개 — 단위·컴포넌트가 13개, E2E가 5개예요. 정작 앱 소스는 9개 파일입니다. 테스트 파일 수가 소스 파일 수와 거의 같다는 게 처음엔 이상해 보이는데, 제대로 굴러가는 프로젝트는 대개 이 근처입니다.

테스트가 앱 코드를 얼마나 지나갔는지 보여주는 비율 — 커버리지는 문장 100%, 분기 86.36%로 끝났습니다. 남은 13.64%는 딱 두 자리인데, 성격이 서로 달라요.

한 자리는 컴포넌트가 화면에서 사라진 뒤에 서버 응답이 뒤늦게 도착하는 경우를 막아두는 가드입니다. 커버리지 편에서 “재현하려면 언마운트 타이밍을 인위적으로 조작해야 하고 로직은 한 줄이니 당장은 안 채워도 된다"고 판정한 자리예요. 다른 한 자리는 검색 결과가 0건일 때의 안내 화면인데, 이건 사정이 다릅니다 — 사용자가 자주 만나는 경로라 채울 가치가 있고, 그래서 21편에서 여러분이 직접 채워보시라고 일부러 비워둔 숙제로 남겼습니다.

그러니 86.36%는 실패한 숫자가 아닙니다. 리포트가 하는 일은 빈 곳을 알려주는 데까지고, 어디를 채울지 정하는 건 사람이거든요. 숫자를 100으로 맞추려고 억지 테스트를 넣으면 늘어나는 건 잡아내는 결함이 아니라 관리할 스위트뿐입니다. 채우지 않기로 한 결정도, 독자에게 넘긴 자리도 전략이에요.

전체 코드는 데모 저장소에 있습니다. 처음 오신 분은 1편부터, 코드만 보실 분은 README의 편↔태그 매핑표를 따라가세요 — git checkout step-N으로 각 편이 끝난 시점의 코드를 그대로 열어볼 수 있어요.

그리고 표보다 중요한 것 — 여러분이 할 수 있게 된 일들입니다. 1편의 자신과 비교하며 체크해보세요.

  • 무엇을 얼마나 테스트할지 스스로 전략을 세운다
  • 낯선 프로젝트에 테스트 환경을 맨손으로 세팅한다
  • 비동기·mock·네트워크까지 아우르는 단위/컴포넌트 테스트를 쓴다
  • 어떤 날만 실패하는 flaky E2E를 트레이스로 추적하고 견고한 로케이터로 고친다
  • 접근성 위반을 role 쿼리와 axe로 자동 검출한다
  • 머지 게이트로 팀의 품질 기준을 코드화한다
  • AI가 만든 코드와 테스트를 검증하고 다듬는다

테스트 경험 0으로 1편을 열었던 분이라면, 지금 이 목록에 전부 체크하고 있는 자신이 꽤 낯설고 — 꽤 자랑스러울 겁니다. 그때 약속했던 도착점, 정확히 여기입니다.

시리즈 완주의 성장 아크를 표현한 계단 다이어그램 - 테스트 경험 0의 1편에서 시작해 단위 테스트의 기초 체력, 사용자처럼 보는 컴포넌트 테스트, 진짜 브라우저의 E2E와 접근성 자동 검사, 팀을 지키는 CI 게이트를 거쳐 AI를 검증하는 감독의 자리까지 다섯 계단을 오르는 모습입니다. 마지막 계단 위에 완주라는 깃발이 있습니다
시리즈 완주의 성장 아크를 표현한 계단 다이어그램 - 테스트 경험 0의 1편에서 시작해 단위 테스트의 기초 체력, 사용자처럼 보는 컴포넌트 테스트, 진짜 브라우저의 E2E와 접근성 자동 검사, 팀을 지키는 CI 게이트를 거쳐 AI를 검증하는 감독의 자리까지 다섯 계단을 오르는 모습입니다. 마지막 계단 위에 완주라는 깃발이 있습니다

테스트 문화 — 도구보다 어려운 건 습관

Vitest도 Playwright도 붙이는 데는 하루면 충분합니다. 정작 오래 걸리는 건 ‘테스트를 쓰는 사람들’이 되는 쪽이에요. ‘테스트가 있는 게 당연한’ 팀은 배포가 두렵지 않고, 리뷰가 건설적이고, 온보딩이 빨라요. 그리고 그 문화는 거창한 선언이 아니라 — PR 하나에 테스트 하나를 얹는 작은 반복에서 시작됩니다.

세 가지가 눈에 띄게 달라집니다. 리뷰에서 “이거 테스트 있어요?“가 공격이 아니라 질문이 돼요. 새로 온 사람이 코드를 이해하려고 테스트 파일부터 여는 광경이 생깁니다 — 테스트는 “이 함수가 무엇을 약속하는가"를 실행 가능한 형태로 적어둔 문서니까요. 그리고 리팩터링 제안이 늘어납니다. 고쳐도 안 깨진다는 걸 확인할 방법이 있으면 손이 가볍거든요.

반대로 문화가 실패하는 방식도 정해져 있습니다. 대개 커버리지 숫자를 목표로 걸 때예요. 80%를 맞추라고 하면 팀은 80%를 맞춥니다 — 다만 19편에서 본 expect(true).toBe(true)의 사촌들로요. 숫자는 올라가고 스위트는 무거워지는데 잡아내는 결함은 그대로입니다. 강제할 거라면 커버리지가 아니라 “버그를 고칠 때 재현 테스트를 함께 넣는다” 쪽이 훨씬 낫습니다.

테스트 없는 레거시 코드에서 시작한다면

여기까지는 ‘처음부터 테스트와 함께 자란 프로젝트’ 이야기였어요. 현실에서 더 흔한 건 반대쪽입니다 — 몇 년째 잘 굴러가고 있는데 테스트는 한 줄도 없는 코드, 흔히 레거시라고 부르는 그것이요. 전부 덮으려 들면 시작도 못 합니다. 저는 이 순서를 권해요.

첫째, 방금 난 버그부터. 새 기능이 아니라 버그가 가장 좋은 출발점입니다. 재현 조건이 이미 밝혀져 있고, 고쳤다는 증거가 필요하고, 무엇보다 “왜 이 테스트를 쓰는가"를 아무도 묻지 않거든요. 버그 하나에 테스트 하나면 스위트는 팀이 실제로 밟은 지뢰의 지도가 됩니다.

둘째, 가장 자주 고치는 파일부터. git log로 변경이 잦은 파일을 뽑아보세요. 자주 고친다는 건 자주 깨진다는 뜻이고, 테스트의 투자 대비 회수가 가장 큰 자리입니다.

셋째, 새 코드에만 기준을 겁니다. 기존 코드는 그대로 두고 “앞으로 추가되는 것에는 테스트를 붙인다"로 선을 그으면 저항이 거의 없어요. 레거시는 건드릴 때마다 조금씩 덮이고요.

셋 다 공통점이 있습니다 — 전부 다 하겠다고 하지 않는 것. 한 번에 덮겠다는 계획은 대부분 두 주를 못 넘깁니다.


채용·경진대회 관점 — 면접에서 무엇을 말할 수 있게 되나

팀 안에서만 쓸모 있는 이야기는 아닙니다. 테스트를 직접 해본 경험은 밖에서도 꽤 잘 팔려요.

면접에서 “테스트 어떻게 짜세요?“는 실력을 가늠하는 좋은 질문이에요. “어디까지 mock하나”, “flaky는 어떻게 잡나” 같은 질문에 자기 경험으로 답할 수 있다면 강력하죠. SW 테스트 경진대회처럼 검증 역량을 겨루는 자리도 결국 무엇을 어떻게 검증하는가의 승부고요. 이 시리즈에서 다진 기본기가 그 출발점이 되면 좋겠어요.

예를 들어 “테스트 전략이 어떻게 되세요?“라는 질문에, 이제 이렇게 답할 수 있죠 — “핵심 로직은 단위 테스트로 촘촘히, 사용자 흐름은 컴포넌트 테스트로, E2E는 크리티컬 패스만 가져갑니다. 접근성은 role 쿼리와 axe를 CI 게이트에 넣어 지키고, 커버리지는 목표가 아니라 빈 곳을 찾는 지표로만 씁니다.” 전부 이 시리즈에서 직접 해본 것들이고요. (여기 나온 ‘크리티컬 패스’는 로그인·검색·결제처럼 사용자가 반드시 지나는 경로를 말합니다. E2E는 느리고 비싸니 이 길목에만 굵게 깔자는 뜻이에요.)

이 답을 하면 대개 꼬리 질문이 따라옵니다. “그럼 어디까지 목으로 대체하나요”, “flaky 테스트는 어떻게 처리하세요”, “커버리지 목표를 안 정하면 팀에서 관리가 되나요” 같은 것들이죠. 셋 다 이 시리즈가 한 편씩 붙잡고 다룬 주제라, 답의 재료는 이미 갖고 계실 겁니다. 중요한 건 정답을 외우는 게 아니라 자기가 내린 결정과 그 이유를 말할 수 있는가예요. “커버리지 86%에서 멈췄고, 남은 분기는 이런 이유로 안 채웠습니다"가 “100% 유지합니다"보다 훨씬 좋은 답입니다.


다음 단계 — 여기서 뻗어갈 네 갈래

여기서 뻗어갈 길은 많습니다. 다만 전부 지금 필요한 건 아니에요 — 언제 이게 필요해지는지를 같이 적어둡니다.

API 계약 테스트(contract testing) — 프론트와 백엔드가 주고받기로 한 응답의 모양을 양쪽에서 검증하는 방식입니다. 앞에서 ‘role 계약’이라고 부른 것과 이름은 겹치지만 상대가 달라요 — 그건 화면이 보조기술에게 한 약속이었고, 이건 프론트가 서버와 맺은 약속입니다. MSW 편에서 우리가 목으로 흉내 낸 응답이 실제 서버 응답과 어긋나지 않는지 확인해주는 자리예요. 백엔드가 다른 팀일 때 값어치가 확 올라갑니다. 혼자 풀스택으로 만들고 있다면 아직 이릅니다.

뮤테이션 테스트(mutation testing) — 테스트의 테스트입니다. 19편에서 손으로 해본 ‘일부러 깨보기’를 도구가 수백 번 자동으로 돌려주죠(Stryker). 스위트가 커져서 손으로 부숴보기가 불가능해질 때, 그리고 AI가 짠 테스트 비중이 늘어날 때 필요해집니다.

성능 회귀 감시 — Lighthouse CI나 Web Vitals를 CI에 붙여 “느려졌다"를 빌드 실패로 만드는 방식입니다. 시각 회귀 편이 보이는 것의 회귀를 잡았다면 이쪽은 체감 속도의 회귀예요. 사용자 이탈이 신경 쓰이기 시작할 때가 신호입니다.

프로덕션 모니터링 — 테스트가 못 잡는 걸 관측으로 잡습니다. 아무리 촘촘히 짜도 실제 사용자의 기기·네트워크·데이터 조합은 재현이 안 되거든요. 테스트를 대체하는 게 아니라 뒤를 받치는 마지막 층입니다.

넷 중 하나만 고르라면, 백엔드가 다른 팀인 환경에서는 API 계약 테스트를 먼저 권합니다. 제 경험상 프론트엔드에서 터지는 사고는 “서버 응답이 생각과 달랐다"로 수렴할 때가 많거든요.


한 장 요약 — 시리즈 전체

22편을 한 화면으로 줄이면 이렇습니다.

  • 무엇을 테스트하나: 구현이 아니라 동작. “이 단언이 깨졌을 때 사용자가 겪는 문제가 있나"를 통과하는 것만 남긴다
  • 어떻게 찾나: 사용자가 화면을 찾는 방식대로 — role과 접근 가능한 이름. 이 원칙 하나가 테스트를 견고하게 만들고 접근성까지 함께 지킨다
  • 얼마나 하나: 단위는 촘촘히, 컴포넌트는 사용자 흐름 단위로, E2E는 크리티컬 패스만. 커버리지는 목표가 아니라 빈 곳을 찾는 지표
  • 믿을 수 있나: 일부러 깨봤을 때 빨간불이 켜져야 진짜다. 초록불만으로는 아무것도 증명되지 않는다
  • 팀에 어떻게 남기나: 사람의 의지가 아니라 CI 게이트로. 강제할 것은 커버리지 숫자가 아니라 “버그를 고칠 때 재현 테스트를 함께 넣는다”
  • AI와는: 생성은 위임해도 책임은 위임할 수 없다 — 초안은 AI, 확정은 사람

긴 여정의 끝에서

여기까지가 처음에 계획한 분량입니다. 다만 끝났다는 뜻은 아니에요.

쓰면서 알게 된 게 하나 있는데, 어떤 편은 저부터 손에 잘 안 잡혔습니다. 코드는 전부 직접 돌려보고 출력을 그대로 옮겼지만, 매일 테스트를 짜는 사람의 감각과 글로 정리한 이해는 아무래도 층이 다르더군요. 그런 편은 아마 읽으실 때도 매끄럽지 않았을 겁니다.

그래서 프론트엔드 테스트 이야기는 앞으로도 계속 쓰려고 합니다. 새 도구를 좇기보다는 여기서 뻑뻑했던 자리를 다시, 더 쉽게 풀어보는 쪽으로요. 한 편에 하나씩 붙잡고, 실제로 부딪히면서요. 읽다가 막힌 대목이 있었다면 댓글로 남겨주세요 — 어디서 걸리는지가 제일 알기 어려운 부분이라, 그게 다음 글의 목차가 됩니다.

테스트를 짜기 시작하면 신기한 일이 생겨요. 코드를 지울 때 더 이상 심장이 쫄깃하지 않습니다. 그 평온함이, 우리가 이 긴 여정을 한 이유예요.

함께 달려주셔서 고맙습니다. expect(당신의_다음_프로젝트).toBe('테스트와 함께') — 여기서 배운 걸 실전에서 만나요.

레벨업: 테스트 경험 0에서 시작해 — 전략부터 CI·접근성·AI 검증까지, 완주입니다.

시리즈 완결. 계획한 22편은 여기까지고, 프론트엔드 테스트 이야기는 계속됩니다.

낯선 용어가 있었다면 — 용어집에 전부 한 줄씩 정리돼 있어요.