← Back to list

실무에서 쓰이는 디자인 시스템 — 스토리북: 테스트 주도 개발

안녕하세요. 오늘은 실무에서 쓰이는 디자인 시스템의 마지막 챕터인 ‘스토리북: 테스트 주도 개발’에 대해 이야기해 보겠습니다.

Duchan Jo · 2024-11-06 06:01 · 0 claps · 11.7 min read
#design-systems #design-system-tips #test-driven-development #front-end-development #storybook
Open on Medium ↗
Wiki topics: PRD · Product Design

실무에서 쓰이는 디자인 시스템 — 스토리북: 테스트 주도 개발

안녕하세요. 오늘은 실무에서 쓰이는 디자인 시스템의 마지막 챕터인 ‘스토리북: 테스트 주도 개발’에 대해 이야기해 보겠습니다.

Chapter

What is Storybook?

Storybook은 UI 개발 과정에서 테스트와 문서화를 지원하는 프론트엔드 도구로, 시각적인 테스트 주도 개발을 가능하게 합니다. 이를 통해 디자인 시스템 구축 시 QA 과정에서 기획자와 디자이너 등 이해관계자가 컴포넌트 단위로 UI를 쉽고 빠르게 테스트할 수 있습니다. 또한, Storybook 자체가 곧 문서 역할을 하므로, 문서를 효율적으로 작성하고 공유할 수 있습니다.

Getting Started

Storybook은 버전에 따라 차이가 클 수 있습니다. 이 글에서는 작성 시점 기준 최신 버전인 8.3.6을 기준으로, ‘React + Vite (TypeScript)’ 환경에서 설명합니다.

// Install
npx storybook@latest init

Exploring the Storybook UI

아래는 Storybook을 실행하면 볼 수 있는 Storybook UI 화면입니다. Storybook UI는 Sidebar, Toolbar, Canvas, Panels로 구성되어 있습니다.

Sidebar

Sidebar는 Storybook의 왼쪽 패널로, 프로젝트 내의 컴포넌트와 스토리 목록 등 계층 구조를 탐색할 수 있는 공간입니다. 이를 통해 사용자는 각 컴포넌트를 선택하고 해당 스토리를 쉽게 탐색할 수 있습니다. 프로젝트의 계층 구조에 대한 자세한 내용은 아래의 ‘Component Naming & Hierarchy’ 섹션에서 설명하겠습니다.

Toolbar

Toolbar는 Storybook의 상단에 위치하며, 컴포넌트 리마운트, 화면 확대/축소, 배경 색 변경, 그리드 등 다양한 기능을 제공합니다. 이 도구들을 통해 사용자는 컴포넌트의 UI를 쉽고 빠르게 테스트할 수 있습니다.

Canvas

Canvas는 Storybook의 중앙 영역으로, 선택한 스토리의 실제 렌더링 결과를 보여줍니다. 이곳에서 컴포넌트의 시각적 상태를 확인하고 사용자 상호작용을 테스트할 수 있습니다.

Panels

Panels는 Canvas 하단에 위치하며, Controls Tab을 통해 Props를 수정하고 즉각적인 결과를 관찰할 수 있습니다. 이외에도 이벤트 핸들러(콜백) 인수를 통해 수신된 데이터를 확인할 수 있는 Actions 등의 기능이 포함되어 있습니다.

Global Settings

Global Settings를 통해 웹폰트 및 전역 스타일 등 다양한 설정을 할 수 있습니다.

Preview

루트 위치의 .storybook 디렉토리 안에 있는 preview 파일의 확장자를 .ts에서 .tsx로 수정한 후, preview 객체 안에 아래 예시와 같이 decorators를 작성합니다. Storybook은 Story를 파라미터로 받아 빈 태그 안에 렌더링됩니다.

// preview.tsx

import type { Preview } from "@storybook/react";
import GlobalStyle from "../src/styles/common";

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
  decorators: [
    (Story) => (
      <>
        <GlobalStyle />
        <Story />
      </>
    ),
  ],
};

export default preview;

Preview Head

루트 위치의 .storybook 디렉토리 안에 preview-head.html 파일을 만들어 웹폰트 등의 CDN을 불러올 수 있습니다.

<!-- preview-head.html -->

<link
  rel="stylesheet"
  as="style"
  crossorigin
  href="someUrl"
/>

Story Files Overview

Stories 파일은 Storybook에서 컴포넌트의 상태를 정의하고 문서화하는 기본 단위입니다. 이 파일을 통해 컴포넌트의 다양한 상태를 시각적으로 테스트하고 문서화할 수 있습니다. 아래는 Stories 파일의 기본 구성 요소에 대한 설명입니다.

import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '@/components/Button';

// Meta 데이터 정의
const meta = {
  title: 'Design System/Components/Button',
  component: Button,
  tags: ["autodocs"],
} satisfies Meta<typeof Button>;

export default meta;

// Story 타입 정의
type Story = StoryObj<typeof meta>;

// Story 정의
export const Primary: Story = {
  args: {
    sort: "PRIMARY",
    label: 'Button',
  },
};

export const Secondary: Story = {
  args: {
   sort: "SECONDARY",
    label: 'Button',
  },
};

Meta 데이터 정의

  • title은 Storybook UI에서 컴포넌트를 그룹화하는 데 사용됩니다. 계층 구조로 컴포넌트를 구성할 수 있으며, 위의 예시는 “Design System” 카테고리 내의 “Components” 폴더의 “Button” 컴포넌트를 나타냅니다. 계층 구조에 대해서는 아래에서 좀 더 자세히 설명하겠습니다.
  • component는 해당 스토리가 연결된 실제 컴포넌트를 지정합니다.
  • tags는 Storybook의 자동 문서화 기능을 활성화하기 위해 사용됩니다. “autodocs”는 자동 문서화를 의미하며, 컴포넌트의 Props 타입 정의 시 JSDoc을 통해 Description을 작성할 수 있습니다. Default는 컴포넌트의 Props의 기본 값에 따라 결정됩니다.

Story 타입 정의

StoryObj는 Storybook에서 제공하는 제너릭 타입으로, meta 객체의 구조와 속성을 기반으로 스토리 타입을 생성합니다.

Story 정의

각 스토리는 컴포넌트의 상태를 나타내며, args 속성을 사용하여 해당 컴포넌트에 Props를 전달합니다. 위의 예시에서는 Button 컴포넌트의 Primary와 Secondary 상태를 각각 스토리로 정의하고 있습니다.

Component Naming & Hierarchy

Storybook의 프로젝트 계층은 Category, Folder, Component로 구성됩니다. 이는 필수 사항은 아니며, 생성 가능한 계층 구조로 이해하셔도 좋습니다. 더 자세한 내용은 여기 링크를 통해 확인할 수 있습니다.

프로젝트 계층은 Stories Files 내의 meta 객체의 title을 통해 생성할 수 있으며, 슬래시(/)를 사용하여 계층을 구분합니다. 이때 서로 다른 Stories Files에서 title이 중복되면, 같은 컴포넌트로 인식되어 병합됩니다.

// Stories Files

const meta = {
 title: 'Design System/Components/Button', // 계층 설정
 tags: ["autodocs"], // Docs 설정
// ...some code
} satisfies Meta<typeof Button>;

export default meta;

// Story 생성
export const Primary: Story = {
// ...some code
};

Hierarchy Order

프로젝트 계층 순서는 “Global Settings” 섹션에서 설명한 preview 파일에서 설정할 수 있습니다. 아래 예시와 같이 preview 객체의 parameters 안에 options을 작성합니다. 예시에서는 Design System 카테고리 내의 폴더를 Pages, Components 순으로 정렬하고, Components 폴더의 첫 번째 컴포넌트를 Header로 지정하고 있습니다.

// preview.tsx

import type { Preview } from "@storybook/react";

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    // 프로젝트 계층 순서 설정
    options: {
      storySort: {
        order: ["Design System", ["Pages", "Components", ["Header", "*"]]],
      },
    },
  },
};

export default preview;

Specifying ArgTypes

Storybook에서 argTypes는 args에 대한 context를 제공합니다. 이를 통해 각 arg에 대해 명시적으로 name, description, control 등을 설정할 수 있습니다. 아래 예시 코드와 같이 argTypes를 해당 컴포넌트 전체 스토리에 설정할 수도 있고, 특정 스토리에 개별적으로 설정할 수도 있습니다. Control types에는 text, number, boolean, check, radio, select 등이 있으며, 더 많은 Control Types은 여기 링크에서 확인할 수 있습니다.

// 해당 컴포넌트 전체 스토리에 argTypes를 설정
const meta = {
  argTypes: {
    size: {
     name: "customName",
     description: "You can provide a description here",
      control: { type: "select" },
    },
} satisfies Meta<typeof Button>;

// 특정 스토리에 개별적으로 argTypes를 설정
export const Primary: Story = {
  argTypes: {
    size: {
     name: "customName",
     description: "You can provide a description here",
      control: { type: "select" },
    },
};

Storybook에서는 Control Panel에서 사용자에게 숨기고 싶은 arg가 있을 수 있습니다. 이럴 때, 아래 예시 코드처럼 argTypes의 table을 이용해 간단한 유틸 함수를 만들어 arg를 프라이빗하게 사용할 수 있습니다.

// 유틸 함수
export function disableProperty(name: string) {
  return {
    [name]: {
      table: {
        disable: true,
      },
    },
  };
}

// 스토리 파일
const meta = {
  argTypes: {
    ...disableProperty("size"),
} satisfies Meta<typeof Button>;

Custom Render Functions

스토리에서 Render 함수를 이용하면 스토리가 렌더링되는 방식을 추가로 제어할 수 있습니다. 이를 통해 스토리에서 컴포넌트를 감싸거나 useEffect, useState와 같은 훅을 사용할 수 있습니다. 더 자세한 내용은 여기 링크에서 확인하실 수 있습니다.

export const Tertiary: Story = {
  args: {
    // args...
  },
  render: function Render(args) {
    const [state, setState] = useState();

    useEffect(() => {
      // some code...
    }, []);

    return <Button {...args} />;
  },
};

Using useArgs

useArgs는 Storybook에서 제공하는 훅으로, 스토리의 args를 동적으로 관리하고 업데이트할 수 있게 해줍니다. 이를 통해 사용자 상호작용에 따라 스토리의 Props 값을 실시간으로 조정할 수 있습니다. useArgs의 동작 방식은 useState와 유사하다고 이해하셔도 좋습니다. 아래 예시와 같이 useArgs의 첫 번째 배열에서는 구조 분해 할당을 통해 isChecked라는 arg 값을 가져와 사용하고, 두 번째 배열에서는 updateArgs를 가져와 업데이트 함수로 사용합니다.

import { useArgs } from '@storybook/preview-api';

export const Tertiary: Story = {
  args: {
    // args...
  },
  render: function Render(args) {
    const [{ isChecked }, updateArgs] = useArgs();

    function onChange() {
      updateArgs({ isChecked: !isChecked });
    }

    return <Checkbox {...args} onChange={onChange} isChecked={isChecked} />;
  },
};

이것으로 ‘실무에서 쓰이는 디자인 시스템’ 시리즈를 마치도록 하겠습니다. 읽어주셔서 감사합니다.

참고

https://storybook.js.org/docs


메타데이터
post_id
46d94b5eb0ee
slug
실무에서-쓰이는-디자인-시스템-스토리북-테스트-주도-개발-46d94b5eb0ee
url
https://medium.com/@duchanjo/%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EC%93%B0%EC%9D%B4%EB%8A%94-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B6%81-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C-46d94b5eb0ee
canonical_url
https://medium.com/@duchanjo/%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EC%93%B0%EC%9D%B4%EB%8A%94-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B6%81-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C-46d94b5eb0ee
author_url
https://medium.com/@duchanjo
status
ok
fetched_at
2026-06-20 20:29:01