TDD(Test Driven Development)의 중요성을 체감하기 전에는 "테스트를 먼저 작성하고 코드를 짜라"는 말이 백엔드 로직에서는 이해가 갔지만, 화면을 그리는 프론트엔드에서는 딱히 와닿지가 않았다.
TDD에 한창 관심이 많을 때, 마침 로고도 눈길이 가고 스토리북이라는 도구명도 흥미로워서 바로 사용해보았다. 처음에는 단순히 컴포넌트 갤러리를 만드는 도구인 줄 알았는데, 이걸 활용하면 UI도 TDD가 가능하다는 사실을 다시금 깨닫게 되었다. 여러 프로젝트에서 직접 적용해보며 느낀 점들을 정리해본다.
기존에 내가 하던 UI 개발 방식은 '구현 → 확인 → 수정'의 무한 반복이었다.
Button 컴포넌트를 일단 만든다.App.js에 넣고 브라우저를 켠다.이 방식의 문제는 '확인' 과정이 너무 귀찮다는 것이다. 특정 모달 창의 버튼을 테스트하려면 매번 모달을 띄우는 과정을 반복해야 했다. 하지만 CDD(Component Driven Development)와 스토리북을 도입하고 프론트 개발의 효율이 달라짐을 느꼈다.
페이지 전체를 한 번에 만드는 게 아니라, 가장 작은 컴포넌트부터 독립적으로 만들고 검증하는 방식에 있어 스토리북은 든든한 존재이다.
내가 경험한 UI TDD의 핵심은 "코드를 짜기 전에, 어떤 모습이어야 하는지 'Story'를 먼저 정의하는 것"이었다.
1단계: Story 정의
예를 들어 LoginForm 컴포넌트를 만든다고 가정해보자. 아직 컴포넌트 파일도 안 만들었지만, 일단..! 이 컴포넌트가 어떤 props를 받아야 하고 어떤 상태를 가져야 하는지 미리 정의한다.
// LoginForm.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { LoginForm } from './LoginForm'; // 아직 안 만들었기 때문에 에러 발생!
const meta: Meta<typeof LoginForm> = {
component: LoginForm,
title: 'Components/LoginForm',
};
export default meta;
type Story = StoryObj<typeof LoginForm>;
// 1. 기본 로그인 폼 상태
export const Default: Story = {};
// 2. 에러가 났을 때의 상태
export const WithError: Story = {
args: {
error: '이메일 형식이 올바르지 않습니다.',
},
};
LoginForm이 없으니 당연히 에러가 날 것.! 이것을 TDD의 Red(실패) 단계라고 할 수 있다.
2단계: 구현
이제 테스트 통과시키기 위해. 즉, 에러를 없애기 위해 컴포넌트를 구현한다. 이때 중요한 건 스토리북 화면만 보면서 개발하는 것이다. 전체 앱을 실행할 필요가 없다.
// LoginForm.tsx
interface LoginFormProps {
error?: string;
onSubmit?: (data: any) => void;
}
export const LoginForm = ({ error, onSubmit }: LoginFormProps) => {
return (
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
{/* 에러가 있으면 빨간색으로 표시 */}
{error && <p style={{ color: 'red' }}>{error}</p>}
<button>Login</button>
</form>
);
};
코드를 저장하면 스토리북에 해당 폼이 나타난다. WithError 스토리를 클릭했을 때 빨간 에러 메시지가 잘 뜬다면 이것이 바로 Green(성공) 단계이다!

이번에 학습하면서 가장 놀라웠던 기능은 play 함수였다. 버튼을 눌렀을 때 로그인이 되는지 확인하려면 예전엔 직접 마우스로 눌러봐야 했지만 스토리북의 play 함수를 쓰면 이 과정을 자동화할 수 있다.
// LoginForm.stories.tsx
import { userEvent, within, expect } from '@storybook/test';
export const FilledForm: Story = {
play: async ({ canvasElement }) => {
// 1. 캔버스 내부 요소 찾기
const canvas = within(canvasElement);
// 2. 사용자 입력 시뮬레이션(타이핑)
await userEvent.type(canvas.getByPlaceholderText('Email'), 'test@example.com', {
delay: 100, // 천천히 입력하는 효과
});
await userEvent.type(canvas.getByPlaceholderText('Password'), 'password123');
// 3. 버튼 클릭
await userEvent.click(canvas.getByRole('button', { name: 'Login' }));
// 4. 검증
await expect(canvas.getByPlaceholderText('Email')).toHaveValue('test@example.com');
},
};
이렇게 작성해두면 스토리북을 켤 때마다 브라우저가 알아서 타이핑하고 클릭하는 모습이 재생된다. 처음 이걸 사용했을 때는 이게 진짜 UI TDD구나 싶었다..
아직 모든 기능을 마스터한 건 아니지만, "UI를 격리해서 개발한다"는 사고방식 하나만으로도 개발의 질도 한 단계 올라간 느낌을 받았다.
이런 경험을 통해 스토리북은 모든 프로젝트에 반드시 적용해야 하는 도구라기보다는, 프로젝트의 규모와 팀 구성, 그리고 유지보수 기간을 고려해 선택해야 할 도구라는 생각이 들었다.