본문 바로가기

프로젝트 개발일지/warrr-ui 디자인 시스템

[warrr-ui 디자인 시스템 개발기] 스토리북과 테스트

이번 주 주제는 스토리북과 테스트였다.

 

사실, 개발하면서 스토리북을 자주 썼지만,,, 여러 기능들을 활용해 본 것도 아니었고, 문서화를 제대로 한 것도 아니었다.

그래서, 스토리북에 대해서 보다 알아가는 시간이 필요하다고 생각했었고, 문서화, 애드온, 테스트 관련해서 어떤 기능들이 있는지 알아보는 시간을 가졌다.

그리고 테스트 관련해서도 정말 자잘하게 단위 테스트 작성해 본 게 다라서 우리 디자인 시스템에는 어떤 테스트를 적용하는 게 좋을지에 대해서도 생각해 보는 시간을 가졌다.

 

먼저 스토리북부터 알아보도록 하자.

 

스토리북 문서화

스토리북 문서화는 상당히 중요하다.

근데 나는 이때까지 제대로 한 적이 없었다. ㅋㅋ 이제부터 열심히 할게요!

어쨌든 중요도를 따지는게 별 의미는 없겠지만, 디자인 시스템에 있어서 자세한 문서화는 중요한데, 우리는 1차적으로 이 스토리북을 문서로 사용하기로 했으니,,, 당연히 일반 웹 페이지를 만드는 프로젝트보다 훨씬 더 문서화가 중요할 것이다.

그래서, 어떻게 하면 보다 효율적으로, 그리고 정확하게 우리가 표현하고자 하는 걸 나타낼 수 있는지에 대해서 집중하면서 공부했던 것 같다.

 

먼저, 스토리북 문서화 관련해서 정말 정리가 잘 되어 있다고 느끼는 블로그 글이 있어서 가지고 왔다.

아래 글을 하나하나 따라하면서 스토리북 문서화 기능을 배웠던 것 같다.

https://iyu88.github.io//storybook/2023/04/07/storybook-docs.html

 

사실 문서화야 저 글 보면서 따라 하면 누구나 할 수 있다고 생각한다.

그런데, 중요한 건 팀에서 어느 정도의 자세한 문서화를 할지 정하는 것이라고 생각했다.

그리고 그에 대한 일관성을 지키는 게 정말 중요하다고 생각했고.

그래서 내 결론은 스토리 코드 템플릿 파일을 만들어서 제공하는 것이었다.

 

저번에 다른 팀원이 plop js를 활용해서 코드 템플릿 파일을 만들어서 코드젠을 사용할 수도 있다고 했을 때가 바로 생각이 났다.

그래서 나도 세팅 연습해볼겸 아래 레포지토리에서 테스트해 봤다.

https://github.com/ghdtjgus76/warrrui-setting-test/tree/main

 

원리를 간단히 설명하자면, plopfile에 있는 제너레이터를 활용하는 것인데, 스토리를 작성할 컴포넌트 이름을 작성하게 되면, templates/Story.tsx.hbs라는 템플릿 파일에 그 컴포넌트 이름이 적용되어서 primitive 버전과 themed 버전이 생기게 되는 것이다.

코드로 알아보도록 하자.

 

아래는 plopfile이다.

사용자에게 프롬프트를 통해서 아래 message를 띄워주고 사용자가 컴포넌트 이름을 입력하게 되면 primitive, themed 폴더에 각각 스토리 파일이 생기게 된다.

export default function (plop) {
  plop.setGenerator("Story", {
    description: "Create a story file",
    prompts: [
      {
        type: "input",
        name: "name",
        message: "스토리를 작성할 컴포넌트 이름을 입력해주세요",
      },
    ],
    actions: [
      {
        type: "add",
        path: "../ui/src/primitive/{{pascalCase name}}/{{pascalCase name}}.stories.tsx",
        templateFile: "templates/Story.tsx.hbs",
      },
      {
        type: "add",
        path: "../ui/src/themed/{{pascalCase name}}/{{pascalCase name}}.stories.tsx",
        templateFile: "templates/Story.tsx.hbs",
      },
    ],
  });
}

 

아래는 스토리 파일 템플릿이다.

지금은 살짝? 부족하겠지만, 문서화를 위한 자잘한 부분들을 추가해 놨다.

물론, 나중에 협업하기 전 공통 템플릿을 정해야 하긴 하겠지만, 이런 느낌으로 템플릿을 정한 후 코드젠을 활용하면 일관성 있게 문서화할 수 있을 것이라 생각했다.

import type { Meta, StoryObj } from "@storybook/react";

// Primitive-UI라면 primitive 경로, Themed-UI라면 themed 경로로 변경
import {{pascalCase name}} from "@/primitive/{{pascalCase name}}";

const meta = {
  // title은 카테고리/컴포넌트명으로 작성, 카테고리는 Primitive-UI 또는 Themed-UI로 작성
  title: "Primitive-UI/{{pascalCase name}}",
  // 컴포넌트명 작성
  component: {{pascalCase name}},
  tags: ["autodocs"],
  parameters: {
    // 컴포넌트 부제목 작성
    componentSubtitle: "버튼 컴포넌트",
    docs: {
      description: {
        // 컴포넌트 세부 설명 작성, props 등 상세한 설명 작성 (Optional)
        component: `- children으로 텍스트 값을 지정하면 됩니다.\n- onClick은 클릭 함수입니다.`
      },
    },
  },
  argTypes: {
    props1: {
      description: 'props1은 default 값이 있는 필수적인 속성입니다.',
      table: {
        // type, 필수가 아닐 경우 required 삭제
        type: { summary: 'string', required: true },
        // defaultValue (Optional)
        defaultValue: { summary: '기본 값이 있으면 여기에 적습니다.' },
      },
      control: {
        // Args Table에서 사용자가 조작할 수 있는 타입
        // select일 경우 options를 배열로 제공
        // 그 외 text, number, boolean, color, check, radio 등 존재
        // control 불가능하게 하고 싶을 경우 객체가 아니라 false로 설정
        // ex) control: false, 
        // 참고 : https://storybook.js.org/docs/react/essentials/controls
        type: 'select',
        options: ['option1', 'option2'],
      },
    },
  },
  // decorators (Optional)
  // 공통적으로 적용하고 싶은 컴포넌트 설정
  decorators: [
    (Story) => (
      <>
        {Story()}
      </>
    ),
  ],
} satisfies Meta<typeof {{pascalCase name}}>;

export default meta;

type Story = StoryObj<typeof meta>;

/**
 * Story 설명 작성
 */
export const Primary: Story = {
  args: {},
};

 

현재 패키지 구조는 아래와 같은데, ui 패키지에서 codegen을 실행시켜서 스토리 파일을 만들어낸다.

 

이 과정은 ui 패키지의 package.json을 살펴보면 좀 더 쉽게 알 수 있다.

아래와 같이 codegen의 plopfile을 활용해서 ui 패키지에서 동작할 수 있도록 정의해 놓았다.

"scripts": {
  // ...
  "plop:codegen": "plop --plopfile ../codegen/plopfile.js"
},

 

그럼 이제 실제로 동작하는 모습을 보도록 하자.

 

대충 저런 식으로 동작한다.

생각만 해도 편할 거 같다.

 

추가적으로, 우리 팀에서는 plop을 활용해 코드젠 기능을 적극 활용하기로 했는데, 스토리 코드 말고도 몇 가지 더 템플릿을 정해서 적용해 보기로 했다.

내가 제안한 사항은 다음과 같다.

1. 스토리 코드

2. 각 컴포넌트의 package.json

 

우리는 각 컴포넌트를 다 따로 배포하기로 결정했기 때문에 해당 컴포넌트별로 다 package.json을 작성해줘야 하는데, 이 번거로운 과정을 없애고자 했다.

이에 대해서는 돌아오는 스크럼 시간에 논의해 보기로 했다.

 

스토리북 테스트

이것도 이제 스토리북 공식 문서 읽으면서 처음 알게 된 게 많은데, 생각보다 스토리북에서 지원하는 테스트들이 많았다.

정확히 말하면 jest, playwright 등 테스트 관련 라이브러리를 한 번 더 감싸서 스토리북 측에서 제공하는? 기능이라고 하면 맞을 거 같다.

 

먼저, 상호작용 테스트(키보드, 마우스, 타이핑 등과 관련된 테스트)이다.

사실, 이것도 jest나 다른 테스트 라이브러리에서도 할 수 있다.

그런데, 스토리북에서 패널을 통해 눈으로 보고 테스트할 수 있다는 장점이 있었다.

https://storybook.js.org/docs/writing-tests/interaction-testing

 

웹 접근성 테스트도 있었다.

이건 애드온을 활용한 것이었는데, 사실 해당 애드온은 playwright와 axe를 내부적으로 사용하는 거라 playwright 웹 접근성 테스트와 별 다를 바가 없었다.

그런데 아래와 같이 스토리북에서 패널을 통해 웹 접근성을 지킨 부분과 지키지 않은 부분을 한눈에 확인할 수 있다는 장점이 있었다.

그리고 이 웹 접근성 테스트하면서 브라우저 테스트도 같이 할 수 있었다.

총 세 개, firefox, chromium, webkit 모두 테스트해 볼 수 있어 playwright의 장점을 다 누릴 수 있을 것이라 생각했다.

https://storybook.js.org/docs/writing-tests/accessibility-testing

 

이제 고민이 생겼다.

우리는 어떤 테스팅 라이브러리를 쓰고, 어떤 스토리북 애드온을 쓸 것인지...

 

내가 내린 결론은 다음과 같다.

1. jest (컴포넌트 로직 검증 + 상호작용 테스트)

2. playwright (웹 접근성 테스트 + 브라우저 테스트)

 

사실 우리는 전체 웹페이지를 개발하는 것이 아니라, 단일 컴포넌트를 개발하고 테스트하는 것이기 때문에 통합 테스트는 필요하지 않을 것이라 생각했다.

그리고, 원래는 위의 애드온을 사용해서 개발해 보는 게 어떤가?라는 생각을 했었는데, 테스트가 단순히 테스트만의 기능을 가지는 게 아니라, 코드 설계 면에서도 좋고, 명세의 기능을 한다는 다른 팀원의 말에 결정을 바꾸게 되었다.

 

그리고, 이번에 돌아오는 스크럼 시간에 테스트 관련 결정 사항도 함께 정하기로 했다.

 

turborepo 컨트리뷰트

어쩌다 보니 내가 터보 레포 컨트리뷰터가 됐다.

그게 어떻게 된 거냐면,,, 디자인 시스템 세팅 테스트를 해보기로 해서 터보 레포의 디자인 시스템 스타터 템플릿을 활용해 볼까 하는 생각이 들었다.

그래서 내부적으로 어떤 방식으로 돌아가는 건지 궁금해서 찾아보다가, package.json을 열게 되었다.

그런데, react가 dependency나 peerDependency가 아니라 devDependency에 들어가 있어서 그냥 discussion을 열게 되었다.

https://github.com/vercel/turbo/discussions/8156

 

당일에 바로 버셀 개고수 개발자가 답변을 남겨주셨다.

PR을 남겨달라는 내용이었다.

그래서 얼른 PR을 남겼고, 아래와 같다.

https://github.com/vercel/turbo/pull/8159

 

아주 사소하지만, 버셀 컨트리뷰터라니,,, 신기했다.

추후 디자인 시스템 개발하면서 보다 개선했으면 좋겠다고 생각한 부분도 버셀에 discussion을 올려볼까 하는 생각이 있다.

이제 오픈 소스 컨트리뷰터도 잔뜩 도전해 보기로 했다.