KwonYG

프론트엔드 테스트

프론트엔드 테스트 관련 주제들을 학습하면서 알게된 내용들

테스팅 프레임워크(Testing Frameworks)

작성 기준 리액트 진영, 가장 대중적인 테스트 프레임워크는 jest + React Testing Library 조합이다.

커스텀 hook 테스트가 필요하면 추가적으로 React Hooks Testing Library를 사용하여 컴포넌트 렌더 없이 테스트 할 수 있지만, 재활용 되지 않는 hook이라면 컴포넌트 테스트에 hook 테스트를 포함할 것을 권장한다.

단위 테스트(Unit Test)

단위 테스트의 범위는 사람마다 의견이 갈리는 부분 중 하나라고 한다. 프론트엔드는 입출력이 명확하지 않은 부분을 테스트하는 경우가 많아 그 경계를 짓기가 어렵다.

하지만 일반적으로 큰 단위의 기능에서 복잡한 로직을 따로 분리한 함수 테스트가 필요한 경우에 작성하고 나머지는 통합테스트를 통해 테스트 비용을 줄이는 방식을 많이 택하는 것으로 보인다.

케이스별로 아래 같은 경우다.

  • 컴포넌트 내에 사용되는 재활용 함수
  • 전체 기능 테스트를 하기에 로직이 복잡한 경우
  • 엣지케이스의 특정 함수가 제대로 동작하는지 확인이 필요한 경우

정리하자면, 특정 컴포넌트의 1회성 함수로 사용될 경우에는 해당 함수만을 위한 단위테스트 보다는 컴포넌트 테스트에 포함시키는 것이 좋고, 복잡한 로직을 포함하는 함수라면 이 때 이 함수의 단위테스트를 고려해볼 수 있다.

통합 테스트(Integration Test)

통합 테스트는 두 개 이상의 컴포넌트, 모듈이 실제로 연결된 상태를 테스트하는 것을 의미한다. 페이지 단위의 컴포넌트나 Form과 같은 다수 컴포넌트 간의 상호작용이 많은 경우 통합테스트 코드로 작성한다.

HTTP Request, Store 등 외부 시스템 테스트를 포함할 수 있다. 이때 HTTP Request, Store 등은 Mocking을 하되, 특정 라이브러리나 외부 모듈에 의존하지 않도록 유저 행동 기반(Behavior Driven)으로 테스트 코드를 작성하는 것이 좋다.

예를 들어, Form 라이브러리를 react-hook-form에서 formik으로 교체하더라도 테스트코드에 영향을 미치지 않도록 작성하는 것이 좋다. (단, test setup이 바뀌는 것은 불가피한 부분이다.)

그래도 의존성이 해결되지 않는다면 구현 코드에 외부 의존성이 강결합 되어있는 경우가 많다는 신호일 수 있다. 프론트엔드에서 의존성을 제어하는 방법이란 글에서 의존성을 걷어내는 부분이 잘 설명되어있다.

E2E 테스트(End to End Test)

사용자 입장에서 GUI를 통해 진행하는 테스트이다. 최종 결과물에서 실제 사용자 시나리오를 작성하고 시나리오 흐름대로 제품에 문제가 없는지 확인하는 테스트이다.

시각적 요소 테스트 용도로도 활용된다. 기본적으로 js-dom은 브라우저 환경이 아니라 window의 width, height이 0인데, 화면 너비에 따라 바뀌는 UI 요소를 jest에서 확인하기 어렵다.

이런 시각적 요소로 테스트 커버리지를 확장할 목적으로도 사용할 수 있다.

E2E 테스트 툴은 셀레니움, cypress가 자주 언급되긴 하지만 playwright와 puppeteer와 같은 웹 자동화 툴을 활용하는 경우도 있다.

오히려 후에 렌더링 포퍼먼스 측정까지 고려한다면 performance tracing을 지원하는 playwright와 puppeteer같은 웹자동화 툴이 더 나은 선택이 될 수 있다. 반면, cypress는 E2E 테스트에만 집중하기 때문에 테스트 결과를 확인하는데 가독성이 좋다.

컴포넌트 테스트(Component Test)

컴포넌트가 목적에 맞게 잘 동작하는지 테스트한다. 현재 컴포넌트의 관심사를 테스트 해야하고 내부 하위 컴포넌트들의 세부 구현 사항, 기능 등은 테스트하지 않는다.

쿼리를 사용할 때에는 공식문서에 가이드된 우선순위대로 사용할 것을 권장한다. 이는 웹 접근성과 관련된 부분이며, 우선 순위가 낮은 쿼리를 자주 사용하게 된다면 이는 접근성이 잘 지켜지지 않고 있다는 신호가 될 수 있다.

퍼블릭 인터페이스에 대해서만 테스트를 작성한다.

특정 외부 라이브러리에 의존하지 않도록 유저 행동 기반(Behavior Driven)으로 테스트 코드를 작성하고, assertion도 내부 상태를 검증하지 않도록 주의하는 것이 좋다. 그 상태가 외부 의존성이 있다면 외부 의존성만 바뀌더라도 테스트는 쉽게 깨진다.

로직을 외부로 추출한 함수나 hook이 있는 경우 재활용 빈도가 낮으면 컴포넌트 테스트 안에 포함하는 것이 좋다.

모킹(Mocking)

jest-mock

  • mocking이 필요한 객체나 함수가 있는 경우 기본적으로 jest에서 제공하는 mock api를 사용할 수 있다.
  • 호출내역 조회, 단순 mock, 특정 상태 임의구현 등이 필요한 경우 Spy, Mock, Stub을 사용해 일종의 Fake 구현체를 만든다.

MSW(Mock Service Worker)

  • 네트워크 레벨에서 요청을 가로채 mocking할 수 있다.
  • 네트워크 레벨을 이용하기 때문에 실제 API 요청처럼 Mock API Response를 받아볼 수 있다.
  • 네트워크 레벨에서 요청을 가로채기 때문에, 어떤 http 관련 라이브러리를 쓰더라도 쉽게 모킹이 가능해진다.

Storage

생각

  • 2013 deview 유석문님 발표에서 TDD 이야기가 잠깐 나온다.
    • 개발자가 작성한 코드가 의도대로 동작하는지 확인하는 것이 TDD, 사용자 입장에서 의도대로 동작하는 것인지 확인하는 것이 ATDD → 연습이 많이 필요한 부분이라고 한다.
  • 많은 회사에서 테스트코드가 프로덕션 코드는 아니라 비교적 중요도가 떨어지는 분위기다.
    • 다른 회사 개발자 이야기를 나누었을 때, 모든 피쳐를 TDD로 개발을 하는 경우는 드물었다. 보통 요구사항이 애매한 경우만 TDD를 한다고 한다.
  • Test Code가 있다는 사실이 중요하고, 방법론은 가능할 때 TDD로 가는게 이상적인거 같다.
  • 테스트 코드 작성이 회사 필수룰이 되었는데, 회사 TL분의 의견을 들어보면 유저 행동 기반으로 100% 의존성을 없애기 힘든 케이스가 있는거 같다.
  • 프론트엔드에서 의존성을 제어하는 방법이란 글이 좋았다.
    • 입출력이 명확하지 않는 부분을 최대한 비순수함수에서 순수함수들을 추출하면 최대한 의존성을 덜어낼 수 있다.
    • jotai 관련 예제가 있는데, 사내에 사용한 코드와 비교해보면 좋을거 같다.
    • 동시에 프론트엔드 외부 의존 생태계가 넓어서 테스트때 고려해야할게 많고 대응방식도 가지각색인게 좀 슬프다.

Reference

All rights reserved.