토이스토리 2기 리뉴얼 출시
출시해서 운영하는 토이 프로젝트. 거기에 결제까지.
토이 프로젝트를 출시하고 운영해야 합니다.
그래야 나 자신이 프로젝트에 깊게 투영됩니다. 출시에 필요한 우선순위를 판단하는 사고 과정, 운영하며 만나는 고개에 대응하는 판단력, 그리고 프로젝트를 운영하며 발생하는 문제를 해결하는 능력이 포트폴리오에 녹아들기 때문입니다.
그래야 포트폴리오는 차별화됩니다. 사람은 모두 다르며, 나 자신이 투영되었기 때문에 차별화됩니다. 또한, 출시해서 운영하는 토이 프로젝트는 더 드물기 때문입니다.
토이 프로젝트는 평균 5개를 시작하나 완료율은 50% 미만이며, 출시 후 운영하는 비율은 10% 미만입니다. 많은 토이 프로젝트가 대체로 비슷한 점을 감안하면, 출시 후 운영을 하지 않으면 변별력이 떨어질 수 있습니다.
그럼 왜 토이 프로젝트를 출시하고 운영하지 못하는 걸까요?
여러 이유가 있습니다. 동료가 없거나 문제가 발생해서, 참여 의지가 떨어져서, 완성도를 높이기 어려워서, 추진력이 부족해서, 프로젝트를 어떻게 전개해야 할지 막막해서 등.
그런데, 종류 별로 묶었을 때, 가장 큰 요인은 사수 역할을 할 코치의 부재로 조사되었습니다. 현업에서도 사수가 없는 경우가 60%가 넘는데, 하물며 협업할 동료끼리 팀을 결성하는 토이 프로젝트 팀에는 사수 역할의 코치가 대부분 없습니다.
푸딩캠프에서 만드는 토이 프로젝트는 다릅니다.
- 출시와 운영에 초점을 맞춘 실전 프로젝트를 수행하여 포트폴리오를 만듭니다.
- 경력 26년차, CTO 출신 코치가 직접 여러분의 사수가 되어 코칭합니다. 멘토나 강사가 아닌, 함께 협업하는 사수가 되어 코칭합니다.
- 결제 시스템을 제공하여 더욱 더 차별화된 토이 프로젝트를 만들 수 있습니다. 많은 입사지원자의 입사지원서와 포트폴리오를 봤지만, 유료 결제가 탑재된 토이 프로젝트는 보질 못했습니다.
- 개발 직군 뿐만 아니라 PM, 디자이너 등 다양한 직군의 참가자가 참여하는 풀스택 프로젝트 팀을 결성합니다.
사용자 정의 훅(Custom Hook) 테스트
React의 사용자 정의 훅(Custom Hook)은 useState, useEffect, useMemo 등 기본 훅들을 조합해 특정 로직을 재사용하도록 만든 훅입니다. 일반적으로 UI와는 직접적으로 무관하므로, 컴포넌트 없이도 이 훅의 로직만 독립적으로 테스트하고 싶다는 상황이 생길 수 있습니다.
(1) 왜 Custom Hook 테스트가 필요한가?
- 복잡한 비즈니스 로직이 들어 있는 훅을 재사용할 때, 매번 컴포넌트로 감싸 테스트하기는 번거롭습니다. Form 처리, API 요청, 웹소켓 연결 등을 예로 들 수 있지요.
- UI와 로직을 분리한 구조라면, 훅만 독립적으로 테스트해 로직이 올바른지 검증합니다.
- UI 변경과 상관없이 훅의 로직이 잘 동작하는지 빠르게 확인할 수 있으므로, 유지보수 및 리팩토링에 유리합니다.
(2) Custom Hook 테스트 도구
1) 훅 테스팅 라이브러리 추가
@testing-library/react-hooks(react-testing-library)의 renderHook을 사용하는 것입니다. @testing-library에 react-hooks는 기본 포함되어 있지 않으므로 별도 설치합니다.
2) 실습 컴포넌트 작성
프론트엔드 실습계의 아이돌, 카운터 컴포넌트로 실습해보겠습니다.
기본적인 테스트도 작성해봅니다.
3) useCounter() 사용자 정의 훅 작성
카운터 컴포넌트의 값을 증가 또는 감소하는 비즈니스 로직을 useCounter() 훅으로 분리하겠습니다.
특이사항으로 decrement 함수가 비동기 함수라는 점입니다. 이는 의도한 것으로 이 함수가 비동기 동작(예를 들면, API 호출을 하는 등)으로 비즈니스 로직을 수행한다고 가상의 상황을 산정한 것입니다.
이제 useCounter() 사용자 정의 훅을 Counter 컴포넌트에 반영합니다.
4) 사용자 정의 훅 테스트
사용자 정의 훅을 테스트하는 데 주요하게 renderHook()과 act() 함수가 사용됩니다.
renderHook() 함수로 사용자 정의 훅을 직접 렌더링하고, 그 결과를 RenderResult 객체로 반환하며, renderHook()이 반환하는 결과에서 result 키의 객체로 접근할 수 있습니다. RenderResult 객체는 현재렌더링 결과로 훅이 반환하는 값을 current로 제공합니다. 그래서 훅이 반환하는 count, increment, decrement에 접근할 수 있지요.
훅 내부에서 useState()가 변화를 일으키는 부분은 리액트의 렌더링 사이클에 영향을 미치므로, act()로 감싸야 합니다. act() 안에서 상태 변화가 일어나 리렌더링이 일어나면 해당 변화까지 프로그램 진행 사이클을 진행시켜서 result.current에 변화가 반영됩니다. 이는 useEvent로 UI 상 상호작용을 하고, 그로 인해 리렌더링이 일어나 렌더링 사이클을 거쳐야 하는 경우에도 act()를 사용해야 합니다. 그렇지 않아도 테스트를 통과하기도 하지만, act() 관련 경고 메시지를 표시하지요.
맨 마지막 테스트를 보면 act()를 사용하는 부분을 비동기로 처리했습니다. 이는 decrement()가 비동기 함수이고, 이를 비동기 처리하기 위해 act()의 인자로 비동기 함수로써 전달하고, act() 자체도 비동기로 실행했습니다. 단, 이는 act()의 비동기 동작 예를 들기 위함이며, act()는 상태 변화로 렌더링 사이클을 사용자 정의 훅 결과에 반영하는 것이 주 용도입니다.
5) 훅 테스트 시 유의점
비동기 로직이 포함된 훅 중에서 시간 소요가 들어가는 경우는 await 비동기 처리와 함께 waitForNextUpdate()나 waitFor()함수를 사용해 상태 변화가 끝날 때까지 기다려야 합니다.
또, Context가 필요한 경우, renderHook() 함수의 옵션에서 wrapper 옵션을 사용해 Provider로 감싸줄 수 있습니다. 예를 들어, 인증 여부를 다루는 useAuth() 훅이 있고, 이 훅은 AuthProvider라는 Provider를 의존한다고 하는 경우 다음과 같이 테스트 하면 됩니다.
(3) Custom Hook 안에서 Mocking이 필요한 경우
사용자 정의 훅에서 API 호출이나 타이머, 브라우저 API 등을 사용하는 경우가 있습니다. 이럴 때도 Mocking 기법을 동일하게 적용할 수 있습니다.
예를 들면, useFetchUser 훅이 fetch를 써서 유저 정보를 가져온다고 할 때, 테스트에서는 실제 네트워크를 호출하지 않도록 global.fetch = vi.fn()으로 Mock 처리하거나, MSW(Mock Service Worker) 등을 사용해 가짜 응답을 제공합니다. 이에 대해서는 다음 편에서 다루겠습니다.
(4) 이외 챙길 요소
1) 훅과 컴포넌트 분리
- 훅에 대한 테스트를 충분히 작성해두면, 컴포넌트 테스트를 간소화할 수 있습니다. UI 배치와 이벤트 연결만 확인하고, 비즈니스 로직은 훅에 대해서만 테스트하는 것입니다.
- 단, 통합 관점에서 전체 화면의 흐름도 별도로 테스트(통합 테스트, E2E 테스트)해봐야 합니다.
2) Mocking과 의존성 주입
- 훅 내부에서 사용하는 함수나 모듈을 분리해 놓으면(예:
3) Lifecycle 주기 확인
- useEffect
- 예를 들어, 부수효과를 일으키는
4) React 18 이후의 이중 호출(Strict Mode)
- 개발 모드에서 Strict Mode가 켜져 있으면,
- 테스트에는 일반적으로 Strict Mode가 적용되지 않거나, 일관성 있게 처리할 수 있도록 설정하는 경우가 많습니다.
의견을 남겨주세요