Skip to content

리액트를 배우셨군요 유감입니다 4편 (테스트, 생태계, 그리고 체념의 미학)

Published: at 오후 03:51

1편부터 3편까지 리액트의 탄생 비화부터 JSX, 컴포넌트, 상태 관리, 그리고 useEffect의 함정까지 정말 뼈를 때리는 내용들이었죠.

오늘은 드디어 이 대장정의 마지막 편입니다.

리액트 개발자들의 정신 건강을 위협하는 ‘테스트’의 세계, 선택 장애를 유발하는 거대한 ‘생태계’, 그리고 이 모든 고통 끝에 찾아오는 ‘체념의 미학’에 대해 이 책이 어떤 이야기를 들려주는지 함께 보시죠.

리액트 테스트, 컴포넌트를 위한 심리치료

리액트 애플리케이션을 테스트하는 건 마치 태풍 속에서 카드로 집을 짓는 것과 같다고 하더라고요.

우리는 코드를 테스트하는 게 아니라, 코드가 DOM 요소가 될지도 모르는 무언가를 표현한 것을 리액트가 추상화한 것을 테스트하는 셈이죠.

이 문장이 복잡하게 들리신다면, 실제 테스트 코드를 보면 기절하실지도 모릅니다.

단 5줄짜리 간단한 컴포넌트를 테스트하기 위해, 우리는 50줄이 넘는 코드로 그 컴포넌트가 만지는 모든 것을 모킹(mocking)해야 하거든요.

// 5줄짜리 컴포넌트
const UserGreeting = ({ userId }) => {
  const user = useUser(userId);
  return <div>Hello, {user?.name}!</div>;
};

// 50줄짜리 테스트 코드
import { render, screen, waitFor } from "@testing-library/react";
// ... 수많은 라이브러리 import
jest.mock("../hooks/useUser", () => ({
  useUser: jest.fn(),
}));

describe("UserGreeting", () => {
  // ... 수많은 사전 설정 코드

  it("displays user name", async () => {
    useUser.mockReturnValue({ name: "John" });

    render(
      <QueryClientProvider client={queryClient}>
        <Provider store={store}>
          <MemoryRouter>
            <ThemeProvider theme={theme}>
              <UserGreeting userId={1} />
            </ThemeProvider>
          </MemoryRouter>
        </Provider>
      </QueryClientProvider>
    );

    await waitFor(() => {
      expect(screen.getByText("Hello, John!")).toBeInTheDocument();
    });
  });
});

5줄을 테스트하기 위해 50줄을 작성하는 것, 이것이 바로 진보입니다.

React Testing Library의 위선

’React Testing Library’의 철학은 “사용자가 사용하는 것처럼 컴포넌트를 테스트하라!”인데요.

현실은 좀 다르죠.

사용자는 버튼을 클릭하고 텍스트를 읽을 뿐, querySelector가 뭔지 알 턱이 없습니다.

이 라이브러리의 슬로건은 “자바스크립트와 DOM API를 알고 소스 코드에 접근할 수 있는 사용자가 사용하는 것처럼 테스트하라!”가 더 정확할 거예요.

게다가 테스트를 실행할 때마다 우리를 괴롭히는 act(...) 경고는 또 어떤가요.

분명히 라이브러리 함수 안에 act가 포함되어 있다고 했는데도, 우리는 여전히 이 경고를 마주하고 act로 코드를 감싸야만 합니다.

리액트 테스팅의 세계에 오신 것을 환영합니다.

여기서는 규칙은 그때그때 만들어지고 경고는 별로 중요하지 않죠.

스냅샷 테스트, 거짓말하는 거짓말 탐지기

스냅샷 테스트는 테스트를 쉽게 만들어 줄 구원투수처럼 보였는데요.

컴포넌트의 렌더링 결과를 파일로 저장해두고, 다음 테스트에서 변경 사항이 있는지 비교하는 방식이죠.

하지만 실제로 일어나는 일은 이렇습니다.

1. 테스트가 실패한다.

2. “컴포넌트가 바뀐 건가? 잘 모르겠는데?“

3. npm test -- -u 명령어로 모든 스냅샷을 업데이트한다.

4. 모든 테스트가 통과한다.

5. 배포한다.


스냅샷 테스트는 당신이 하는 모든 말에 동의해주는 친구와 같습니다.

전혀 도움이 되지는 않지만, 기분은 좋게 만들어주죠.

생태계, 똑같은 것을 만드는 47가지 방법

리액트 생태계는 마치 모든 요리가 스파게티인 뷔페와 같거든요.

면발이 서로 다르게 엉켜있을 뿐이죠.

모든 결정에 대해 47가지의 선택지가 주어지고, 각각이 ‘최고의 방법’이라고 주장하는데요.

하나를 고르고 나면 세 개의 새로운 선택지가 나타나고 당신이 고른 것은 이미 구식이 되어 있습니다.

프레임워크부터 CSS, 폼까지

리액트 프로젝트를 시작하는 것부터가 전쟁인데요.

한때 표준이었던 ‘Create React App’은 이제 공식적으로 버려졌고, 그 자리를 ‘Next.js’, ‘Vite’, ‘Remix’ 같은 프레임워크들이 차지했습니다.

단순한 블로그를 만드는데 분산 시스템에 대한 이해가 필요하게 된 셈이죠.

CSS를 작성하는 방식도 마찬가지인데요.

‘styled-components’, ‘Emotion’, ‘CSS Modules’, ‘Tailwind’ 등 수많은 방법론이 서로 자기가 최고라고 외치고 있습니다.

당신의 CSS는 이제 CSS를 생성하는 자바스크립트가 되었죠.

HTML 폼이 너무 단순했나요?

걱정 마세요.

‘React Hook Form’, ‘Formik’ 같은 라이브러리들이 당신을 위해 준비되어 있습니다.

단순했던 <form> 태그는 이제 수십 줄의 코드와 유효성 검사 스키마, 그리고 복잡한 상태 관리 로직으로 대체되었죠.

npm audit이라는 보안 연극

npm install을 실행할 때마다 마주치는 수백, 수천 개의 취약점 경고는 또 어떤가요.

$ npm install
found 1,746 vulnerabilities (1,698 low, 47 moderate, 1 high)
run `npm audit fix` to fix them

$ npm audit fix
fixed 0 of 1,746 vulnerabilities
1,746 vulnerabilities remain

이것이야말로 보안 연극의 정수입니다.

대부분의 ‘취약점’은 개발용 의존성의 정규식 DoS 공격 가능성이거나, 당신이 전혀 사용하지 않는 패키지의 프로토타입 오염 같은 것들이죠.

하지만 저 빨간색 경고 메시지는 정말 사람을 불안하게 만듭니다.

옆집 잔디는 항상 푸르다? (그리고 다시 리액트로 돌아오는 이유)

모든 리액트 개발자는 “분명 더 나은 방법이 있을 거야”라고 생각하는 시기를 겪게 되는데요.

맞습니다.

더 나은 방법은 존재하죠.

하지만 당신은 아마 결국 리액트를 다시 쓰게 될 겁니다.

채용 시장이 그렇게 말하고 있으니까요.

‘Vue’는 리액트와 앵귤러의 장점을 합친 합리적인 프레임워크이고요.

‘Svelte’는 가상돔도 런타임도 없는 혁신적인 컴파일러입니다.

‘SolidJS’는 리액트처럼 생겼지만 훨씬 빠르죠.

‘HTMX’는 자바스크립트 없이 서버가 내려준 HTML만으로 인터랙션을 구현하는, 웹의 근본으로 돌아가자는 혁명적인 아이디어입니다.

이 모든 대안들은 각자의 방식으로 리액트보다 뛰어나거나 단순한데요.

하지만 우리는 결국 리액트로 돌아옵니다.

왜냐하면 95%의 채용 공고가 리액트를 요구하고, 가장 큰 생태계와 커뮤니티를 가지고 있으며, 이미 우리는 리액트라는 납치범을 사랑하게 되는 ‘스톡홀름 증후군’에 걸렸기 때문이죠.

체념, 리액트와 평화롭게 공존하는 법

부정, 분노, 타협, 우울을 거쳐 드디어 우리는 ‘수용’의 단계에 이르렀습니다.

이것은 패배나 항복이 아니라, 현실을 받아들이고 그 안에서 평화를 찾는 지혜죠.

리액트와 함께 제정신을 유지하는 첫걸음은 바로 ‘싸울 전투를 고르는 것’인데요.

빌드 시스템이나 완벽한 컴포넌트 구조 같은 것들과 싸우는 대신, ‘번들 사이즈’나 ‘불필요한 리렌더링’처럼 실제로 사용자 경험에 영향을 미치는 문제들과 싸워야 합니다.

그리고 모든 문제에 리덕스를 적용하려 들지 말고, 가장 단순한 해결책(useState)부터 시작해서, 고통이 정말 심해질 때만 더 복잡한 도구를 꺼내 들어야 하죠.

리액트의 모든 기능이 나쁜 것은 아닙니다.

‘컴포넌트 구성’이나 ‘선언적 렌더링’ 같은 개념은 분명히 복잡한 UI를 조직화하는 데 도움이 되고요.

Next.js나 React Query 같은 생태계의 도구들을 현명하게 사용하면 생산성을 크게 높일 수 있죠.

마치며 당신은 리액트 개발자가 되었다, 유감이지만

축하합니다.

당신은 마침내 리액트를 배우셨습니다.

이제 당신은 컴포넌트, 상태, props, 훅, 그리고 이 모든 것들이 만들어내는 실존적 공포를 이해하게 되었죠.

당신은 이제 리액트 개발자입니다.

심심한 위로의 말씀을 전합니다.

이 책의 저자가 마지막으로 전하는 메시지는 이것인데요.

리액트는 완벽하지 않습니다.

심지어 좋다고 말하기도 어렵죠.

하지만 어디에나 있고, 일자리를 구하게 해주며, 거대한 생태계의 지원을 받습니다.

당신은 리액트를 사랑할 필요도, 옹호할 필요도 없습니다.

그저 생산적으로 사용하고, 제품을 출시하고, 퇴근하면 그만이죠.

결국 이기는 사람은 아무도 없습니다.

우리 모두 이 부조리한 산업 속에서 최선을 다하고 있을 뿐이죠.

이제 가서 무언가를 만드세요.

그리고 node_modules 폴더에 대해서는 되도록 생각하지 마시고요.

괜찮습니다.

모든 게 다 괜찮을 거예요.