목록으로

Subscription 컴포넌트에 대한 단위 테스트 작성하기, 그리고 웹 표준과 시맨틱 웹

시리즈
2024. 12. 15. PM 9:00:00

Subscription 컴포넌트에 대한 단위 테스트 작성하기

테스트 시나리오 구상

Subscription 컴포넌트는 아주 단순한 동작을 하지만, 엄밀하게 동작을 테스트한다면 여러 상황이 가능합니다.
  • 기본 Props를 전달하면 예상한 렌더링 결과를 반환한다
  • 글자를 입력하면 이메일 입력 변경을 반영한다.
  • 구독 버튼을 클릭하면 입력한 이메일을 전달한다.
  • 함수를 자식 노드로 전달하면 예상한 렌더링 결과를 반환한다.
  • 이메일이 비어있으면 자동으로 입력 창에 포커스를 준다.
  • 이메일이 비어있지 않으면 자동으로 입력 창에 포커스가 적용되지 않는다.
일부 시나리오는 굳이 테스트하지 않아도 되지만, 테스트 코드를 연습하는 목적으로 테스트 코드를 작성해보겠습니다.

테스트 시나리오 구현하기

테스트 코드 틀

먼저 테스트 파일을 만듭니다. Subscription.jsx 파일이 있는 경로에 Subscription.test.jsx 파일을 만들고 다음 코드를 작성합니다.
자, 테스트 코드를 작성해나갈 틀을 구성했습니다. 어떤 테스트인지를 기술하는 describe() 함수 안에 test() 함수를 채워나갈 것입니다. 각 테스트 함수는 앞서 정의한 테스트 시나리오 각 각에 짝지어졌다고 보시면 돼요.
셸에서 pnpm test를 입력해 실행해보세요. 테스트가 실행되고, 파일 중 변동이 있는지 지켜보고 있습니다. 켜놓은 상태에서 테스트를 작성하고 저장해보세요. 저장할 때마다 변동사항에 대한 테스트 코드를 자동으로 수행합니다. Failed 라는 메시지와 함께 테스트가 실패하다가 코딩 작업하는 중 passed 메시지가 표시되면 기분이 무척 짜릿하답니다.

기본 Props를 전달하면 예상한 렌더링 결과를 반환한다.

렌더링이 잘 되는지는 렌더링했을 때 화면에 나와야 할 요소가 렌더링 됐는지를 검사하면 됩니다.
컴포넌트를 렌더링하는 함수는 render()로, JSX 문법으로 리액트 노드를 인자로 전달합니다. render() 함수는 RenderResult 객체를 반환하는데, 이 객체에 있는 멤버 중 container는 HTML의 document DOM 객체와 비슷한 역할을 합니다. 물론 실제로는 리액트 DOM의 객체이기 때문에 HTML DOM의 document와는 다릅니다.
container 객체엔 렌더링 된 정보가 DOM 형태로 저장되어 있으며, DOM 다루듯이 리액트 DOM을 다룰 수 있습니다. Subscription 컴포넌트가 문제 없이 렌더링되면 “이메일 주소를 입력하세요” 문구가 placeholder로 표시하는 input 요소가 표시되며, Props와 자식 노드에 따라 출력되는 요소가 있습니다. 테스트 코드에서 자식 노드는 h2 이고, Props로 전달된 버튼 Label은 “구독하기”입니다. 테스트 코드는 이에 대한 검증을 했습니다.

글자를 입력하면 이메일 입력 변경을 반영한다.

글자를 입력하면 입력한 DOM 요소의 값이 반영됩니다. 이건 뻔하고 자명해서 사실 테스트 코드가 별 의미는 없습니다. 하지만, 입력값에 따라 해당 input 컴포넌트 상태가 변화한다면 이러한 테스트가 필요할 것입니다.
RenderResult 객체에는 다양한 유틸리티 함수가 있습니다. 분류를 하자면 크게 세 종류가 있지요.
함수 유형
요소가 존재할 때
요소가 없을 때
여러 요소가 일치할 때
비동기 동작
getBy*
요소 반환
오류 발행
오류 발생
동기식
queryBy*
요소 반환
null 반환
오류 발생
동기식
findBy*
Promise로 요소 반환
Promise reject
오류 발행
비동기식
getBy*는 요소가 반드시 존재해야 할 때 사용합니다. queryBy*는 요소가 있는지 없는지 확인할 때 유용하죠. findBy*는 비동기적으로 나타나는 요소를 테스트할 때 사용하며, Promise 기본 제한 시간은 1000ms 입니다.
예를 들어, placeholder 값이 “이메일 주소를 입력해주세요”인 요소가 반드시 있어야 하므로 getByPlaceholderText 함수를 사용했습니다. 만약 없다면, 예를 들어, placeholder 문자열을 변경하면 오류가 발생하여 이 테스트는 실패하게 되지요.
userEvent는 뒤에서 자세히 설명하고, 이 단계에서는 사용자 상호작용을 하는 데 사용하는 도구 정도로 알아두겠습니다. userEvent.setup()을 호출해 상호작용할 사용자 객체를 하나 생성합니다. 그리고 사용자가 키를 타이핑하는 것을 시뮬레이션 하기 위해 type() 메서드를 사용하여 이메일 주소 하나를 입력합니다.
입력을 마친 후 input 요소의 값(value)을 확인하면 우리가 방금 입력한 이메일 주소인 걸 확인할 수 있습니다.

구독 버튼을 클릭하면 입력한 이메일을 전달한다.

이번엔 이메일 주소를 입력한 후 “구독하기” 버튼을 누르면 Props로 전달된 콜백 함수를 호출하는지를 테스트 합니다.
const onSubscribe = vi.fn() 이 무엇인지에 대해서는 다음 편에서 자세히 다루기로 하고, 여기에서는 목(Mock) 객체를 사용해 모킹(Mocking)하는 것 정도로 알아두세요. 목 객체인 onSubscribe를 Props로 전달하고, 이메일 주소를 입력한 후 “구독하기” 버튼을 누르면 입력한 이메일 주소를 인자로 받아 onSubscribe 콜백 함수가 호출되어야 합니다. 이를 코드로 기술한 것이 위 코드입니다.

함수를 자식 노드로 전달하면 예상한 렌더링 결과를 반환한다.

Subscription 컴포넌트는 자식 노드가 함수이면 해당 함수를 제목과 현 email 상태값을 인자로 전달하며 실행합니다. 제목은 “학습과 성장 컨텐츠 소식”이지요. 이 테스트 역시 첫 번째 테스트처럼 화면에 렌더링되어야 할 요소가 있는지 검사하는 방식으로 테스트를 작성하면 됩니다.
두 번째 querySelector를 보면 DOM Selector가 조금 복잡해보입니다. 리액트 웹 프로그래밍을 한다면 좋든 싫든 웹, 그 중에서도 DOM, CSS에 대해서는 숙지하는 게 좋습니다. 리액트 테스트 코드 작성하는데 CSS Selector 문법이 불쑥 나오니 말이죠. 혹시 긴가민가 하실 분을 위해 설명해드리면 요소 속성(Element attribute)인 role의 값이 "paragraph", role-label의 값이 "현재 이메일"인 p 컴포넌트를 찾는 것입니다.

이메일이 비어있으면 자동으로 입력 창에 포커스를 준다.

Subscription 컴포넌트는 사용자가 이메일 주소를 입력하지 않은 채 가만히 5초 이상 있으면 자동으로 이메일 입력란에 포커스를 두어 입력을 촉구합니다. 이러한 타이머 관리도 테스트 코드에서 모사됩니다. 바로 vi 객체에 있는 advanceTimersByTime()입니다.
이 메서드에 5초(5000ms)를 인자로 전달하면 5초 동안 시간을 보냅니다. 시간이 지난 후에 현 document의 현 활성 요소가 input 컴포넌트인지 확인합니다. document는 실제 HTML DOM은 아니고, JSDOM의 document 객체로 시뮬레이션 된 객체입니다. vitest.config.js 파일에 다음과 같은 설정이 있어서 사용 가능한 것이지요.

이메일이 비어있지 않으면 자동으로 입력 창에 포커스가 적용되지 않는다.

마지막 테스트는 이메일 입력란에 내용이 있으면 자동으로 포커스하지 않는 동작을 테스트합니다. 이번 테스트는 활성 요소(activeElement)를 명확하게 구분하기 위해 이름 입력란(input)을 하나 더 둡니다.
테스트는 먼저 이메일 입력란에 이메일을 입력합니다. 그리고 곧바로 이름 입력란에 PuddingCamp 문자열을 입력하지요. 이제 활성 요소는 이름 입력란입니다. 그 상태에서 5초를 기다린 뒤 다시 확인합니다. 물론 제대로 동작한다면 여전히 이름 입력란이 활성 요소이며, 기대한(expect) 대로 동작합니다.
우리의 테스트 여섯 개 모두 통과하였습니다!

userEvent

userEvent는 사용자 상호작용을 시뮬레이션하는 도구입니다. 이 라이브러리는 브라우저에서 실제 사용자 상호작용이 발생할 때 일어나는 이벤트를 모사하지요.
userEvent는 단순히 DOM 이벤트를 일으키는 것이 아니라 실제 사용자의 행동을 모방합니다. 그리고 사용자의 동작 하나에 대해 관련된 여러 이벤트를 차례대로 발생시키지요. 사용자의 행동을 모방하는 동작이기 때문에 당연스럽게도 실제 상호작용이 가능한 요소에 대해서만 상호작용이 일어납니다. 가령, 숨겨진 요소이거나 비활성화 된 버튼, 인풋에 입력이 일어나지 않습니다.
userEvent와 비슷한 도구로 fireEvent가 있습니다. userEvent가 fireEvent보다 더 높은 수준으로 추상화를 제공합니다. 더 사용자 상호작용과 비슷한 동작과 흐름으로 테스트를 하고자 한다면 userEvent를 권장합니다.
다음은 주요 API입니다.
  • setup() : userEvent 인스턴스를 구성하고 시작합니다.
  • click() : 요소를 클릭하는 동작을 시뮬레이션합니다.
  • type() : 텍스트 입력을 시뮬레이션합니다.
  • selectOptions() : <select> 요소에서 옵션을 선택하는 동작을 시뮬레이션합니다.
  • clear() : 입력 필드를 지우는 동작을 시뮬레이션합니다

웹 표준, 시맨틱 웹, 그리고 테스팅

웹 표준(Web Standards)이란?

웹 표준은 웹 페이지와 웹 애플리케이션을 개발할 때 따라야 할 규범적 지침과 기술 사양의 집합을 뜻합니다. 대표적으로 W3C(World Wide Web Consortium)에서 제정한 HTML, CSS, JavaScript의 표준 사양과 ARIA(Accessible Rich Internet Applications) 같은 접근성 관련 표준들이 있습니다. 웹 표준을 준수하면 브라우저와 기기 종류, 운영체제에 상관 없이 일관성 있는 사용자 경험을 (이상적으로는) 제공합니다.

시맨틱 웹(Semantic Web)이란?

시맨틱 웹은 웹 문서와 애플리케이션에 “의미(시맨틱)”를 부여해, 시각적으로 정보를 보여주는 것에 그치지 않고, 기기(컴퓨터) 또한 그 구조와 의미를 이해하고 활용할 수 있도록 돕는 개념입니다. 예를 들어, 단순히 <div> 태그로 모든 것을 감싸는 대신, <header>, <nav>, <main>, <article>, <section>, <footer>와 같은 시맨틱 태그를 적절히 사용하여 문서 구조를 명확히 하면, 검색 엔진이나 스크린 리더 등의 보조기기는 물론, 테스트 도구 또한 해당 요소의 역할과 의미를 보다 명확하게 파악할 수 있습니다 . 이는 접근성과 SEO(검색 엔진 최적화)를 강화할 뿐만 아니라, 자동화된 테스트에서 요소를 식별하고 상호작용하는 과정을 수월하게 만듭니다.

왜 웹 표준과 시맨틱 웹을 알아야 하는가?

웹 프론트엔드 테스팅은 단순히 “코드가 동작하는가?”를 넘어 “코드가 올바르게 구현되었는가?”, “접근성과 호환성이 확보되었는가?”, “향후 리팩터링과 유지보수가 수월한가?”를 살펴보는 과정입니다. 웹 표준을 준수하고 시맨틱한 마크업을 적절히 활용하면, 다음과 같은 장점이 있습니다.
  • 테스트 시 요소 선택자 관리 용이 : 시맨틱 태그를 사용하면 테스트 코드에서 DOM 요소를 직관적이고 안정적으로 선택할 수 있습니다. 예를 들어, 무수한 <div> 속에서 특정 요소를 찾는 것보다 <nav>, <article> 같은 명확한 의미의 태그로 구조화된 DOM, 그리고 role, aria 속성으로 요소의 역할과 의미를 구현하면 테스트 코드를 유지보수하는 데 큰 도움을 줍니다.
  • 접근성 및 호환성 테스트 향상 : 접근성 표준(ARIA)이나 시맨틱한 구조를 잘 갖춘 페이지는 자동화된 접근성 테스트 도구가 더 정확히 문제를 식별하기에 좋습니다. 이를 통해 장애를 가진 사용자나 다양한 기기 환경에서도 제대로 작동하는지를 손쉽게 검증할 수 있으며, 이는 프론트엔드 품질 관리를 향상시킵니다.
  • 브라우저 간 이슈 최소화 : 웹 표준에 맞춰 구현하면, 브라우저 간 렌더링 차이를 최소화할 수 있습니다. 이는 다양한 환경에서 테스트해야 할 때 발생하는 혼란을 줄이고, 테스트 커버리지를 효율적으로 높이는 데 도움이 됩니다.

프론트엔드 테스팅 역량 강화로 이어지는 이유

  • 코드 품질 향상 : 웹 표준을 준수하면서 시맨틱한 마크업을 사용하면 결과물 자체의 품질이 높아져, 테스트 과정에서 발생하는 불필요한 디버깅 시간과 이슈들을 줄여줍니다. 특히 요소의 동작이 HTML DOM에서 기인하는 것인지, 리액트 DOM에서 기인하는지를 빠르게 구분할 수 있고, 때로는 이미 브라우저에서 높은 완성도로 제공하는 기능에 대해 바퀴의 재발명하는 수고를 덜기도 합니다.
  • 테스트 자동화 효율성 : 명확한 구조와 의미가 담긴 DOM은 Cypress, Playwright, Jest+Testing Library 등의 자동화 테스트 도구들이 요소를 식별하고 상호작용하기 더 쉬운 환경을 제공합니다. 이는 곧 안정적인 테스트 환경 구축으로 이어집니다.
  • 지속 가능한 코드베이스 : 표준 준수와 시맨틱 마크업은 팀원들이 쉽게 이해하고 협업할 수 있는 토대를 만듭니다. 이는 프론트엔드 코드의 수명과 유지보수성을 높이며, 테스트 전략 역시 장기적으로 관리하기가 용이해집니다. 가령 다음 이미지는 시맨틱하지 않아 원하는 요소를 구분하기 어려운 예시입니다. (네, 맞습니다. 푸딩캠프의 마크업입니다...)

결론

웹 표준과 시맨틱 웹에 대한 이해는 “잘 보이는 웹페이지”를 만드는 것을 넘어, 향후 자동화 테스트, 접근성 검증, SEO 최적화, 유지보수성 강화 등 다양한 관점에서 당신이 맡은 프론트엔드 개발 업무를 한 단계 성장시켜줄 수 있습니다. 웹 표준과 시맨틱 웹을 충분히 학습하고 코드에 반영한다면, 프론트엔드 테스팅의 품질과 효율이 눈에 띄게 개선될 것 입니다.
푸딩캠프 뉴스레터를 구독하면 학습과 성장, 기술에 관해 요약된 컨텐츠를 매주 편하게 받아보실 수 있습니다.
이전 컨텐츠
다음 컨텐츠
목차