이 주제를 선택한 이유

Label list page에는 label filter가 존재한다. label filter에는 복잡한 로직이 숨어있는데 label filter를 state로 관리하며 url query string, api query string 으로 변환하는 로직을 포함하는데 filter 갯수는 또 많은지라 복잡한 로직이 포함된다. 로직이 복잡한 터라 수정사항이 있을때 버그가 발생하기 쉬우며 이를 방지하기 위해 UI test를 진행하는 도중 마딱드린 문제점들을 기반으로 해결방안을 모색해보았다.

기존 코드의 문제점들

기존 Label filter쪽 코드의 문제점은 다음과 같다.

복잡한 의존성

Sahara에는 context api가 다양하게 사용되고 있다. route 관련, auth 관련 등등…(훨씬 더 많다) 또한 hooks가 대중적으로 사용되면서 sahara 코드에도 많은 hooks들이 사용되고 있다. 이처럼 hook과 context api를 사용하며 로직 분리나 전역 상태관리를 쉽게하며 가독성이나 props drilling을 해결할 수 있었지만 test를 진행하면서는 복잡한 mock provider를 만들어야 하는 이슈가 생겼다.

제어할 수 없는 Render 로직

복잡한 의존성과 관련된 문제이다. Context api를 전역상태관리 라이브러리 대용으로 사용한다지만 단점이 있다. Redux와 같이 구독 형태가 아니기에 context api에서 사용하는 state의 변경이 있을시에 하위 컴포넌트가 필수적으로 렌더링 된다는 것이다. 심지어는 context api 끼리의 의존성도 존재하기에 순서도 중요하다. 덕분에 불필요한 렌더링은 늘어나고 Test code를 짜며 파악할 수 없는 render 순서 때문에 고생을 했다.

디자인 시스템에 일관성이 없는 컴포넌트가 있음

현재 디자인 시스템에 변경사항이 있을때마다 FilterDetail이라는 Test가 깨지고 있다. 이는 디자인 시스템에서 Vanilia extract 라는 라이브러리를 사용하고 있는데 이 라이브러리는 Build time에 css를 생성하기 때문에 다음과 같은 플로우로 Test가 깨지고 있다.

디자인 시스템 변경사항 있음 → 디자인 시스템 새로 빌드 → 새로운 Css unique id가 생성됨 → Filter detail에서 저장해둔 snapshot의 css unique id랑 다름 → 테스트 깨짐

이 때문에 디자인 시스템에 변경이 있을때마다 snapshot update를 해줘야하는 번거로움이 있다.

해결방안

기존 코드의 변경없이 하려다 보니 위와 같은 문제점들을 마주쳤고 코드 리팩토링 없이는 진행할 수 없다고 판단하였다. 코드 리팩토링의 방향성은 다음과 같다.

Container/Presentational 패턴 이용하기

복잡한 의존성을 제거하기 위해선 Container/Presentational 패턴을 사용하면 쉽게 제거할 수 있다. 예를 들어 Filter UI test를 위해선 Filters 라는 컴포넌트를 렌더하고 테스트하였다. 이 컴포넌트는 props를 안받고 context api와 hooks만을 이용해서 데이터를 저장하고 사용하고 있었다. Filters라는 컴포넌트에서 context api와 hooks를 들어내고 FiltersContainer에 이식한 후 Filters라는 컴포넌트는 props로 기존 정보를 받게된다면 Filters는 context api와 hooks에 의존하지 않게되고 기존 Test code에서 복잡했던 mock provider를 삭제할 수 있게된다. (복잡했던 render 순서도 어느정도 안정화 될듯)

useEffect 사용 최소화하기

복잡한 render 순서나 로직에 가장 큰 기여를 하는 것이 useEffect 인 것 같다. 렌더 순서나 로직을 논리적으로 계산하기 힘들게 할 뿐만 아니라 남용할 경우에 가독성도 떨어지는 경우가 많다. useEffect를 불가피하게 사용하는 경우가 있기야 하다만 useMemo를 사용하여 대체할 수 있는 코드는 최대한 바꾸어야 할 것 같다.

Snapshot test 피하기