! 주의 : TIL 게시글입니다. 다듬지 않고 올리거나 기록을 통째로 복붙했을 수 있는 뒷고기 포스팅입니다.
이번 주말 알라딘에 갔다가
프런트엔드 개발을 위한 테스트 입문이
라는 책을 사서 읽어보는 중인데요
프런트엔드 테스트 기초 지식부터 단위 테스트, UI 컴포넌트 테스트, E2E 테스트 등여
러 테스트 전략과 예제를 통한 테스트 작성법 등을 다루는 책입니다
요즘 AI의 도움으로 테스트 코드를 제가 직접 한 줄 한 줄 작성하지 않고도 정말 빠르
게, 제가 원하는 대로 검증할 수 있는 테스트를 작성할 수 있게 됐고
이 덕분에 근래들어 많은 테스트 코드를 이미 일상적으로 작성하고는 있는데요
그래도 이 책을 사본 이유는
- 제 의도대로 잘 검증할 수 있게 되어있는지 알아볼 수 있도록 테스트 코드 리터러 시를 높이고
- 상황에 맞는 테스트 코드 전략과 그 작성법을 알아보고
- 또는 제가 몰랐던 테스트 작성 시 꿀팁이나 안티패턴이 있을지
이런 것들을 발견할 수 있었으면 하는 마음에, 가볍게 읽어보려고 샀습니다
이 글과 다음에도 이어질 시리즈에서는 "실제로 테스트 코드 작성법"보다는 "이런 전략이 있고, 이런 것에 주의하자"같은 것을 써두려고 합니다.
기술 자체의 사용법보다는, 선배님들이 먼저 겪고 풀어둔 문제들 위주로요
ps. 저는 Frontend를 "프론트"라고 보통 말하는데 책에서 "프런트"라고 하니까 이번 글에서는 그냥 따릅니다.
1. 테스트를 작성해야 하는 이유
테스트 코드? 그런건 도시 전설이지. 나는 실무에서 한 번도 본 적이 없어. 엑셀에 테스트 케이스 정리하고 스크린샷이나 남기지.
≪소프트웨어 장인정신≫(산드로 마쿠소 저) 에서도, ≪이펙티브 엔지니어≫(에드먼드
라우 저) 에서도, 이 책에서도 나오는 테스트 코드에 대한 흔한 오해 또는 인식인데요
특히 이 책에서는 "UI를 다루는 엔지니어들이 테스트 무용론을 자주 가지더라" 라고
언급하기도 합니다.
테스트 환경이나 유저 시나리오를 테스트 코드로 재현하기 어렵다고 느끼기 때문이겠
죠?
저도 모를 때는 막연히 "FE 테스트는 의존성/환경 특성 상 비용 대비 효율이 안 나오
겠지"라고 생각하기도 했었습니다
세 책을 읽어보았을 때 이런 인식을 갖게 되는 원인은 몇 가지 있는데
- 테스트 코드를 작성할 줄 모르니 어렵게 느낀다
- 팀 문화로 정착되지 않았다. 또한 정착되기 힘든 상황이다
- 테스트를 작성해야 하는 이유(작성했을 때의 효과)를 잘 모른다.
이런 느낌입니다
이 책에서는 우리가 이런 "새로 배워야 하는 부담", "더 많은 시간을 쓴다"라는 트레이드 오프에도 불구하고
테스트를 작성해야 하는 이유에 대해 설명하는데, 아래와 같습니다
- 사업의 신뢰성: UI나 시스템의 버그는 서비스의 이미지와 직결한다.
- 유지보수성: 테스트 코드가 없으면 이미 구현이 끝난 기능에 문제가 생길지도 모른다는 불안감에 리팩터링하기 무섭다.
- 코드 품질 향상: 어떤 구현 코드의 테스트 작성이 어렵다면 해당 코드가 너무 많은 역할을 한다는 신호일 수 있다.
- 추가로, 테스트 코드는 웹 접근성을 높이는 데에도 기여한다.
- 잘 작성된 UI 컴포넌트 테스트는 웹 접근성으로부터 테스트 대상을 선택하며 이는 스크린 리더 등의 보조 기기를 사용하는 사용자들에게도 콘텐츠가 인식 가능하다는 증명이 된다.
- 추가로, 테스트 코드는 웹 접근성을 높이는 데에도 기여한다.
- 원활한 협업: 코드가 제대로 동작하는지 테스트가 없으면 직접 구동해 확인해야 한다
- 테스트 코드는 글로 작성된 문서보다 우수한 사양서다
- 테스트가 통과했다면 해당 사양대로 잘 작동한다고 예상 가능하다
- CI(지속적 통합)에서 테스트 통과 후 리뷰하는 규칙이 있다면 리뷰->수정->리뷰 사이클을 줄일 수 있다.
- 회귀 테스트 감소: 모듈을 작게 나누면 한 모듈의 책임은 줄고 테스트도 쉬워지지만, 모듈이 많아지면 의존관계가 생기고, 의존 관계에 있는 모듈이 변경되었을 때 미치는 영향을 검증하기 위해 자주 회귀 테스트를 해야 한다. 자동화 테스트는 이를 줄이는 최적의 방법이다.
2. 테스트 방법과 테스트 전략
프런트엔드 개발의 테스트 범위 분류와 목적
프런트엔드 개발의 테스트 범위(Level)은 크게 네 가지로 분류하는데요
- 정적 분석: 타입스크립트 또는 ESLint가 제공하는 기능 활용.
- 각 모듈 내부의 검증 + 인접 모듈 연계 사용할 때의 문제점까지 검증
- 단위 테스트: 한 가지 모듈에 한정하여 해당 모듈이 제공하는 기능을 검증
- 독립된 환경에서 검증하므로 코너 케이스 (실제로 애플리케이션을 사용할 때 거의 발생하지 않는 케이스)를 검증하기에 적합
- SPA 개발에서 UI 컴포넌트의 경우, 마치
Props를 입력받고HTML/JSX블록을 출력하는 일반적 함수와 같은 방법으로 테스트 가능
- 통합 테스트: 모듈 조합으로 제공되는 기능을 검증.
- 범위가 넓을 수록 효율적인 테스트가 가능하지만 상대적으로 대략적인 검증에 그침
- 예를 들어
셀렉트 박스를 조작한다->목록 화면 내용을 갱신한다와 같이 "인터랙션/기능을 결합"하는 형태
- E2E 테스트: 헤드리스 브라우저와 UI 자동화 도구를 결합하여 검증
- 가장 광범위한 통합테스트
- 실제 애플리케이션을 사용할 때와 가장 유사한 테스트이기도 함
테스트 목적에 따라서도 분류해볼 수 있는데요
-
기능 테스트(프런트에서는 보통 인터랙션 테스트)
- 실제 브라우저가 없어도 가상 브라우저 환경으로 UI 컴포넌트에 대한 인터랙션 테스트를 진행 가능.
- ex.
버튼을 클릭하면 콜백 함수가 호출된다, 문자를 입력하면 전송 버튼이 활성화된다, ..
-
비기능 테스트(프런트에서는 보통 접근성 테스트)
- ex.
명암비가 4.5:1 이상이어야 한다, ..
- ex.
-
회귀 테스트: 특정 시점을 기준으로 전후 차이를 비교
테스트 전략 모델
보통 단위 테스트는 비교적 빠르게 실행되고, 테스트 환경이 고립적이기에 안정성이 높지만, 실제 애플리케이션 사용과의 유사성은 낮습니다.
반대로 E2E 테스트쪽에 속할 수록 비교적 느리게 실행되며, 테스트 환경에 의존성이 많아져 안정성이 낮지만, 실제 애플리케이션 사용 시나리오와의 유사성이 높습니다.

보통 아래와 같이 상층부 테스트 비중이 높은 아이스크림 콘 형태는 안티 패턴으로 자주 언급됩니다.

마이크 콘의 저서 ≪경험과 사례로 풀어낸 성공하는 애자일≫에 따르면
테스트는 피라미드 형태로 비중이 잡히는게 좋은데, 아래와 같은 이유를 언급합니다.
- 상층부 테스트는 브라우저를 포함하므로 실행시간이 길어 신속성이 떨어진다.
- 하층부 테스트는 실행시간이 짧아 신속하고, 자주 실행할 수 있어 안정성도 높다.
- 상층부 테스트가 주를 이루어 실행시간이 길어지는 경우를 생각해보자. 모든 테스트가 통과하는 데 수십 분 이상이 들고, 테스트가 불안정해 몇 가지 테스트가 가끔 실패(flaky)한다면?
- 개발 흐름에 병목을 만들고 곧 실행 빈도를 줄이거나 결과를 기다리지 않게 된다.
- 실행 빈도를 줄이면 테스트 자동화의 신뢰성은 낮아지고, 결과를 기다리지 않게 되면 테스트 자동화의 의미가 없다.
또는 테스팅 트로피 전략 모델도 있습니다. 이는 Testing Library를 개발한 켄트 C. 도즈가 주장했는데요
- 통합 테스트 비중이 가장 높아야 한다고 주장합니다.
- FE 개발에서 단일 UI 컴포넌트로 구현되는 기능은 거의 없고, 단일 기능(ex. 버튼을 누르면 API 호출) 단위도 여러 컴포넌트의 조합으로 구현되므로
- 유저 시나리오를 기점으로 한 통합 테스트 비중이 높을 수록 더욱 우수한 테스트 전략이라는 의도가 담겨있습니다.

제가 처음 "테스트 코드를 도입해봐야겠다" 시도했을 때 실패 경험을 이야기해보자면
당시 E2E 테스트를 먼저 작성한 적이 있습니다. 마음만 앞서서 "핵심 유저 시나리오를 fully 검증하는 테스트를 구성해야겠다" 싶었던 건데요
그런데 인증/인가, API 호출, 페이지 이동, 결제 모듈 의존성, 지연 및 랜덤성, ... 등 많은 불안정한 요소로 인해
테스트가 간헐적으로 실패하곤 했습니다
게다가 실행 시간은 또 매우 길어서(특히 Github Action에서 병렬없이 실행하니) PR이 열리고 E2E테스트가 시작된 후 20분이 지나서야 E2E테스트 결과 - 51건 성공, 3건 실패, 5건 flaky 라고 오곤 했습니다.
전형적인 아이스크림 콘 테스트 전략의 형태로 실패한 케이스에 해당하겠네요
3. 처음 시작하는 단위 테스트
Jest로 단위 테스트를 작성해보는 내용인데요, 책에서는 아래와 같은 내용들을 다룹니다
- Jest로 테스트 인프라 세팅하기, 테스트 실행하기, 결과 분석 및 디버깅하기
- 모듈 호출의 결과를 assertion하는 기본적인 테스트 케이스
- 분기 및 예외 assertion
- matcher: 문자열 배열 객체 등등.
- 비동기 처리를 테스트하기
인데 책 내용을 다 가져올 수도 없고 기초적인 임플란트니까 생략합시다.
4. Mock 객체
테스트는 실제 환경과 유사할수록 재현성은 높지만, 실행 시간이 많이 걸리거나 환경 구축이 어려워질 수 있습니다.
대표적으로 웹 API에서 취득한 데이터를 다루는 상황이 있겠죠??
이럴 때 중요한 사실은, 테스트하는 대상은 웹 API 자체가 아니라 취득한 데이터에 대한 처리라는 것입니다.
이 취득한 데이터의 대역으로 사용하는 것이 Mock 객체(Test Double) 입니다.
Mock 객체 용어
제라드 메스자로스의 저서 ≪xUnit 테스트 패턴≫에서 분류하기를:
- 스텁(stub): 주로 대역으로 사용
- 의존중인 컴포넌트의 대역
- 정해진 값을 반환하는 용도
- 테스트 대상에 할당하는 입력 값
- ex. "웹 API에서 이런 값을 반환받으면 이렇게 작동해야 한다"를 재현하기 위해, 테스트 대상이 스텁에 접근하면 스텁은 정해진 값을 반환하게 한다.
- 스파이(spy): 주로 기록하는 용도.
- 함수나 메서드의 호출 기록
- 호출된횟수나 실행 시 사용한 인수 기록
- 테스트 대상의 출력 확인
- ex. 인수로 받은 콜백함수를 검증한다 (실행된 횟수, 실행 시 사용된 인수, 출력, ..)
이외에도 Fake, Dummy, Mock 등 몇 가지 분류가 더 있는데, 여기서 자세히 다루진 않습니다.
이만 여기까지 하고 5장부터는 다음에 하겠습니다
5장은 "UI 컴포넌트 테스트"고, 그 다음으로 "테스트 커버리지"에 대한 내용, 그 다음으로는 "React/Next.js 통합 테스트"가 있네요
암튼 마칩니다