코드숨 3주차 주간회고
Facts (사실, 객관)
- 과정에서 제일 기대했던 부분인 테스트 작성이 시작되었다.
Feelings (느낌, 주관)
어려웠다.
- 상태를 관리하고 있는
<App />
컴포넌트에선 무엇을 테스트 해야 하는지가 - 어디서부터 어디까지 테스트를 작성해야 하는지가
Findings (배운 점)
유틸 함수의 경우에는 테스트를 작성하고 있지만 리액트 컴포넌트에 대한 테스트 작성은 이번이 처음이었다. 예전에도 지금에도 리액트 컴포넌트에 대한 테스트가 어렵게 느껴졌던 이유는 아무래도 내가 직접 통제하기 어려운 state나 useEffect 같은 리액트 라이프 사이클 때문이었다.
익히 널리 알려진 개념이지만 다시 한번 짚어보면, 테스트를 어렵게 만드는 요소는 최대한 밖으로 밀어내야 다른 것이라도 테스트를 작성하기 편해진다. 지난 번에 팀 내부에서 나왔던 이야기인 ‘테스트를 작성할 줄 아는 것보다 테스트를 작성하기 쉬운 코드를 작성하는 것이 더 중요하다’에 해당한다. 이를 도와주는 것으로 기존의 class 컴포넌트형에서는 Container 패턴이 해당될 수 있겠고, hook에서는 커스텀 훅이 해당될 수 있겠다.
근데 마음 한 구석으로는 이 패턴 자체가 하기 싫은 학습지 숙제를 최대한 미루는 거랑 비슷하단 생각이 들기도 하다 ㅋㅋㅋ
어디서부터 어디까지 테스트를 작성해야 하는지?
- 궁극적으로, 각 컴포넌트의 관심사를 테스트한다. 따라서 컴포넌트 내부의 하위 컴포넌트들의 세부 구현 사항, 기능 등은 테스트하지 않는다. 왜냐면 그들은 하위 컴포넌트의 관심사이지 현재 컴포넌트의 관심사가 아니기 때문.
React Testing Library (RTL) 컨셉
세부적인 구현사항 보다 실제 사용자 경험과 유사한 흐름의 테스트를 목표. 따라서 이 데이터가 h1 태그로 렌더링 되었는지 보다는 이 데이터가 (ex.텍스트) 그대로 잘 렌더링 되었는지를 확인한다
expect(container).toHaveTextContent(
'이렇게 특정 텍스트가 렌더링 되었는지를 확인'
);
리액트 테스트에서 이벤트를 발생시키는 방법
- HTML element event vs fireEvent
IDE의 지원을 받아 click 메소드가 있음을 알게되었다. 이걸 이용해서 테스트를 해보니 실제로도 잘 동작하고 잘 통과되었다. 다른 점이라면 전자는 말 그대로 HTML 요소에 대해 DOM 이벤트를 편하게 발생시키기 위한 API이고, 후자는 리액트 테스트를 위해 테스팅 라이브러리에서 제공하는 API이다. 즉, ‘테스트’라는 목적을 위해서는 후자가 더 다양한 편의성을 지원한다. fireEvent가 두 번째 인자로 이벤트 프로퍼티를 받는 것처럼 말이다. - fireEvent vs userEvent
우선 기본적으로 userEvent는 fireEvent를 바탕으로 한다. fireEvent는 가장 베이직한 api이다. 다만 다른 점은 userEvent가 좀 더 진짜 유저 행동같이 테스트할 수 있다. 예를 들어, fireEvent로 버튼을 클릭하면 버튼에 포커스가 잡히지 않지만 userEvent는 잡힌다.
Container에선 무엇을 테스트 해야 하는가?
- 대부분 api 콜이나 상태관리를 한다. 그런데 이들은 테스트하기가 애매하다.
- 이왕 리액트를 쓰기로 한 거 리액트에서 제공하는 것들은 굳이 테스트 안 해봐도 되는 것 아닐까? 리액트에서 이미 다 보장하고 있지 않을까?
- 테스트를 하는 외부에서 접근하기가 까다롭다. 대부분의 경우, 인자/prop을 넘겨주지 않고 내부에서 로직이 진행되기 때문이다.
이런 고민을 했었고 과제에선 상태관리를 <App />
에서 하는 만큼 기능의 실제 정상 동작 여부를 테스트했다. 하위 컴포넌트에선 단순히 컨테이너에서 내려주는 상태 업데이트 함수를 잘 호출하고 있는지를 테스트했다.
과제 해설에서도 이 부분이 제일 궁금했는데, 개념적으로는 Container에 해당하는 컴포넌트의 테스트의 경우 주로 아래 내용을 테스트하신다고 한다.
- 아주 기본적인 것 체크
- 데이터 플로우가 잘못 되었거나, 오타가 났거나
테스트의 기준은 구현 상세가 아니라 개발 요구사항
내부적으로 state 업데이트가 잘 되고 있는지 그런 상세 구현에 대한 테스트 메세지 작성은, 개발 요구사항은 그대로인데 구현 방법이 달라질 경우 깨지는 테스트를 만든다.
테스트를 작성하는 이유 중 하나는 리팩토링을 쉽게 하기 위함이다. 테스트코드를 믿고 리팩토링을 하기 위함이므로 개발 요구 사항을 기준으로 테스트를 작성하는 것이 보다 적합하다.
다만 이렇게 할 경우 e2e 테스트와의 차별점이 궁금해졌다. 특히 리액트 테스팅 라이브러리는 그 컨셉부터가 e2e 테스트랑 비슷하게 느껴졌기에 더더욱. 지금으로서는 cypress 같은 e2e테스트와의 차이점으로 속도를 말할 수 있을 것 같다.
컴포넌트 사용 설명서에 가까운 테스트
테스트를 작성하다보면, 이 컴포넌트를 어떻게 사용하는지에 대한 설명서에 가까워진다. 즉 (개발자가 이 컴포넌트를) 어떻게 쓰는가, 사용법에 초점을 맞추어 테스트를 작성한다
Container에서 관리하는 상태를 업데이트 하는 함수를 자식 컴포넌트에서 사용하고 있다고 가정하자. 자식 컴포넌트의 테스트 코드에선 해당 함수의 정상 호출 여부를 테스트할 것이다. 이때 사용할 수 있는 matcher로는 toBeCalledWith
도 있을 것이고 그냥 단순히 toBeCalled
도 있을 것이다. 넘겨주는 인자가 있다면 전자를 사용할 것이고, 아니라면 후자를 사용할 것이다. 이 차이를 통해 테스트코드를 읽는 사람은 이 함수를 어떻게 사용해야하는지 알 수 있다. 인자를 따로 넘겨주지 않아도 알아서 상태관리를 통해 업데이트를 하나보다, 까지 생각할 수 있게 된다.
fixture
그동안은 임시 데이터, 상수 데이터를 관리할 때 파일/폴더명을 constant, mockData, stub 뭐 이런 걸로 지어왔었는데 테스트 세계에선 fixture라는 이름을 사용함. cypress에서도 이 이름을 사용한다.