PromleeBlog
sitemap
aboutMe

posting thumbnail
리팩토링을 지원하는 테스트 구성 - React로 시작하는 TDD 12편
Configuring Tests to Support Refactoring - TDD with React Part 12

📅

🚀

들어가기 전에 🔗

우리가 테스트 코드를 작성하는 가장 큰 이유 중 하나는
리팩토링
(Refactoring)을 마음 편하게 하기 위해서입니다.
코드를 좀 더 깔끔하게 정리하고 싶은데, 혹시나 기능이 망가질까 봐 겁이 나서 못 건드리는 경우가 많기 때문입니다.


그런데 막상 코드를 고치니 테스트가 와르르 깨져버린다면 어떨까요?
기능은 멀쩡한데 테스트가 빨간 불을 낸다면, 그것은
깨지기 쉬운 테스트
(Brittle Test)입니다.
오늘은 코드를 수정해도 끄떡없는, 우리를 든든하게 지켜주는
단단한 테스트
를 만드는 비결을 알아보겠습니다.

🚀

나쁜 예: 구현 세부 사항 테스트 🔗

가장 흔한 실수는 사용자가 알 필요 없는
내부 사정
을 테스트하는 것입니다.
간단한
토글 버튼
(Toggle Button)을 예로 들어보겠습니다.

테스트하기 나쁜 습관 🔗

이런 것들은 코드 작성자가 마음만 먹으면 언제든지 바꿀 수 있는
구현 세부 사항
(Implementation Details)입니다.
이걸 테스트하면 변수명 하나만 바꿔도 테스트가 실패하게 됩니다.

🚀

좋은 예: 사용자 행동 중심 테스트 🔗

반면에 좋은 테스트는
결과
에 집중합니다.
내부 변수명이 isVisible이든 show든, 클래스명이 on이든 active든 사용자는 관심이 없습니다.
오직
화면에 내용이 나오는지
만 중요합니다.

🚀

1단계: 테스트 작성 (변하지 않는 것에 집중) 🔗

src/components/ToggleButton.test.tsx 파일을 생성합니다.
우리는 내부 구현(State 이름 등)은 전혀 신경 쓰지 않고, 오직
클릭했을 때 텍스트가 보이는지
만 검증하겠습니다.
src/components/ToggleButton.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, test, expect } from 'vitest';
// 컴포넌트는 잠시 후에 만듭니다.
import ToggleButton from './ToggleButton';
 
describe('ToggleButton 리팩토링 내성 테스트', () => {
 
  test('버튼을 클릭하면 숨겨진 메시지가 나타나고, 다시 클릭하면 사라진다', async () => {
    const user = userEvent.setup();
    render(<ToggleButton />);
 
    const toggleBtn = screen.getByRole('button', { name: '토글' });
 
    console.log('[Step 1] 초기 상태 확인');
    // 처음에는 메시지가 없어야 합니다. (queryBy 사용)
    const hiddenMessage = screen.queryByText('짜잔! 숨겨진 메시지입니다.');
    expect(hiddenMessage).not.toBeInTheDocument();
 
    console.log('[Step 2] 버튼 클릭 (열기)');
    await user.click(toggleBtn);
    screen.debug(); // 화면 출력
 
    // 클릭 후에는 메시지가 보여야 합니다.
    expect(screen.getByText('짜잔! 숨겨진 메시지입니다.')).toBeInTheDocument();
 
    console.log('[Step 3] 버튼 클릭 (닫기)');
    await user.click(toggleBtn);
    screen.debug(); // 화면 출력
 
    // 다시 클릭하면 메시지가 사라져야 합니다.
    expect(screen.queryByText('짜잔! 숨겨진 메시지입니다.')).not.toBeInTheDocument();
  });
});

🚀

2단계: 초기 구현 (Green) 🔗

이제 테스트를 통과시키기 위해 가장 일반적인 방법으로 구현해 보겠습니다.
useState를 하나 사용하여 상태를 관리합니다.
src/components/ToggleButton.tsx
import { useState } from 'react';
 
export default function ToggleButton() {
  // 상태 이름을 'show'라고 지었습니다.
  const [show, setShow] = useState(false);
 
  const toggle = () => {
    setShow((prev) => !prev);
  };
 
  return (
    <div>
      <button onClick={toggle}>토글</button>
      {/* show 상태가 true일 때만 메시지 표시 */}
      {show && <p>짜잔! 숨겨진 메시지입니다.</p>}
    </div>
  );
}

테스트 실행 및 결과 🔗

터미널에서 npm test를 실행합니다.
Terminal
npm test -- src/components/ToggleButton.test.tsx
테스트 결과
테스트 결과
[Step 1] 초기 상태 확인
[Step 2] 버튼 클릭 (열기)
<body>
  <div>
    <button>토글</button>
    <p>짜잔! 숨겨진 메시지입니다.</p>
  </div>
</body>
 
[Step 3] 버튼 클릭 (닫기)
<body>
  <div>
    <button>토글</button>
  </div>
</body>
 
PASS src/components/ToggleButton.test.tsx
아주 잘 통과합니다.

🚀

3단계: 과감한 리팩토링 (Refactor) 🔗

어느 날 팀장님이 와서 이렇게 말합니다.
'변수명이 show가 뭡니까? isVisible로 바꾸세요. 그리고 로직도 커스텀 훅으로 분리합시다.'
일반적인 경우라면 코드를 고치면서 테스트도 같이 고쳐야 할까 봐 걱정했을 겁니다.
하지만 우리는
사용자 행동
만 테스트했기 때문에 걱정 없습니다.
과감하게 코드를 뜯어고쳐 봅시다.

코드 대수술 🔗

src/components/ToggleButton.tsx
import { useState } from 'react';
 
// [리팩토링] 로직을 커스텀 훅으로 분리하고 변수명을 바꿉니다.
function useToggle(initialValue = false) {
  // 변수명 변경: show -> isVisible
  const [isVisible, setIsVisible] = useState(initialValue);
  const toggle = () => setIsVisible(v => !v);
  return { isVisible, toggle };
}
 
export default function ToggleButton() {
  // 기존 useState 코드를 삭제하고 훅을 사용합니다.
  const { isVisible, toggle } = useToggle();
 
  return (
    <div>
      <button onClick={toggle}>토글</button>
      {/* 변수명이 바뀌었지만 화면에 그리는 결과물은 똑같습니다 */}
      {isVisible && <p>짜잔! 숨겨진 메시지입니다.</p>}
    </div>
  );
}

🚀

4단계: 테스트 재실행 (안정성 확인) 🔗

테스트 코드는
단 한 글자도 수정하지 않았습니다.
이 상태에서 다시 테스트를 돌려봅시다.
Terminal
npm test -- src/components/ToggleButton.test.tsx

실행 결과 🔗

 PASS  src/components/ToggleButton.test.tsx
놀랍게도 여전히
PASS
입니다!
이것이 바로 리팩토링을 지원하는 테스트입니다.
내부적으로 변수명이 바뀌든, 훅으로 분리되든, 심지어
Redux
를 쓰든 간에,
버튼을 누르면 글자가 나온다
는 사실은 변하지 않았기 때문입니다.

🚀

결론 🔗

소스 코드는 GitHub - Promleeblog/react-tdd-setup 에서 확인할 수 있습니다.
Terminal
git clone https://github.com/PROMLEE/my-tdd-app.git
cd my-tdd-app
git checkout part12
오늘은 리팩토링을 두려워하지 않게 해주는 테스트 작성법을 배웠습니다.
이제 여러분은 어떤 변화에도 흔들리지 않는 튼튼한 테스트를 만들 수 있습니다.
다음 시간에는
13편. 프론트엔드 테스트 유형 정리
를 통해, 우리가 배운 단위 테스트 외에 통합 테스트, E2E 테스트 등 전체적인 숲을 보는 시간을 갖겠습니다.

참고 🔗