개발 기술/개발 이야기

Storybook, UI 컴포넌트를 테스트하게 해줘!(feat. vite)

by GicoMomg (Lux) 2023. 1. 15.

💡 이번 시간에는 UI 컴포넌트를 테스트할 수 있는 Storybook이라는 툴에 대해 알아보았다!

 

1. storybook이란

1) 스토리북은 무엇일까?

  • 스토리북UI 컴포넌트의 동작을 독립적인 환경에서 테스트할 수 있는 툴이다.
  • 스토리북에 추가된 컴포넌트는 서비스와 독립된 환경에서 렌더링되기에, 재사용 가능한 컴포넌트여야 한다.
  • 스토리북은 개발자가 컴포넌트의 동작을 파악하는데 도움을 준다.
  • 또한 figma 연동도 가능하기에 디자이너와의 커뮤니케이션에도 효과가 있다.
  • 아래 gif는 스토리북의 예시이다.

🤔 컴포넌트를 테스트하기 위해 스토리북을 쓰는 거라면, 이 툴을 쓰지 않고 서버를 구동해서 컴포넌트 동작을 확인하면 안될까? 그렇게 해도 된다.

  • 하지만 재사용가능한 컴포넌트가 UI 컨셉에 맞게 구현되어 있는지 여부를 알고 싶거나
  • UI 컴포넌트의 커스텀 속성이나 동작을 알고 싶거나
  • 특정 로직을 거쳐야 접근 가능한 컴포넌트인 경우(회원탈퇴시 보이는 안내문) 스토리북이 유용하다.

 

2) 스토리북 구성

💡 스토리북에는 “스토리”라는 개념이 존재하는데,
스토리는 UI 컴포넌트의 랜더링된 상태를 캡처한 걸 말한다.
개발자는 스토리북 서버를 구동하여, 각 스토리의 동작이나 UI를 확인할 수 있다.
스토리북 서버를 구동하면 아래와 같은 화면이 나오는데 크게 Canvas, Docs로 구성된다.

(1) Canvas 영역

구성  설명
A. Story 리스트  - 추가한 Story목록이다.
B. 미리보기 영역  - 해당 Story를 미리 보거나, 마우스 인터렉션을 통해 이벤트를 확인할 수 있다.
C. 애드온 영역  - StoryBook에서 애드온을 추가하여 기능 확장이 가능하다.
  (대부분의 스토리북 기능은 애드온으로 구현)
 - Controls 애드온의 경우, Story의 이벤트 체크는 물론,
  props 값을 변경하여 스타일을 확인할 수 있다.

 

(2) Docs 영역

구성  설명
D. Docs  - 스토리에 대한 Props, 이벤트에 대한 설명을 볼 수 있다.
 - 자동 생성되며, 커스터마이징할 수 있다.

 

3) 스토리북 환경설정하는 방법

💡 vite + vue3 환경에서 스토리북을 어떻게 설정하는지 알아보자

(1) 환경 구축하기

  • yarn으로 vite를 설정한다.
yarn create vite

 

  • 그 다음 아래 명령어를 사용해 storybook 설정을 한다.
npx sb init --builder @storybook/builder-vite

yarn add -D @storybook/cli

npx storybook@next automigrate

 

  • 만약 sass기반으로 스타일링을 한다면, sass를 추가하자.
yarn add -D sass

 

  • yarn storybook을 했을 때, 아래 이미지와 같은 화면이 나오면 성공이다!
yarn storybook



(2) Story 작성하기

💡 스토리북 설정도 했겠다.
예시 컴포넌트(BaseInput.vue)의 스토리를 작성해보자! (레포)
src/components/ui/BaseInput.vue 코드는 더보기에서 여기서 확인 가능하다.

  1. src/stories에 BaseInput.stories.js를 추가한다.
  2. 컴포넌트 스토리에 대한 기본 설정을 한다.(title, component, argTypes)
import BaseInput from "../components/ui/BaseInput.vue";

// 컴포넌트 스토리 기본 설정
export default {
  title: "Example/BaseInput", // 스토리 제목
  component: BaseInput,       // 사용할 컴포넌트 등록
  argTypes: { // 컴포넌트의 props 목록, 스토리북에서 조작할 수 있도록 설정
    label: "",
    placeholder: "",
    onInput: {},
  },
};

 

  1. 컴포넌트 템플릿을 정의한다.
    스토리북에서 컴포넌트 렌더링시, 어떤 데이터를 바인딩하고 어떤 형태로 보여줄지 정의하는 것이다.
// 컴포넌트 템플릿 정의

const Template = (args) => ({
  components: { BaseInput },
  // args는 setup() 메서드를 통해 템플릿에 매핑되어야 함
  setup() {
    return { args };
  },
  // 그리고 `args`는 `v-bind="args"`형태로 컴포넌트에 바인딩됨
  template: '<base-input v-bind="args" />',
});

 

  1. 상세 스토리를 정의한다.
// 상세 스토리를 정의

export const NameInput = Template.bind({});  // 앞서 정의한 템플릿을 사용함
NameInput.args = { 
  label: "이름",
  placeholder: "이름을 입력하세요",
};

 

  1. 서버를 구동해보면, 아래와 같은 화면을 볼 수 있다.
    개발자는 Controls를 사용해 데이터를 변경하거나 Actions에서 이벤트를 확인할 수 있다.






2. storybook 구현시 주의사항

💡 UI 컴포넌트를 독립된 환경에서 볼 수 있는 툴이라니! 너무 좋은데?
그런데 이 Storybook을 구현할 때 주의해야할 몇 가지 있다. 같이 알아보자~

(1) 컴포넌트를 데이터에 종속시키지 말자

  • 스토리는 독립된 환경에서 작동되어야 한다.
  • 그러나 만약 스토리에서 렌더링하는 컴포넌트 내부에 api나 route 로직이 있다면?
  • 해당 컴포넌트만 분리해서 스토리에 등록시 무수히 많은 에러를 보게될 것이다 😖
  • 만약 데이터가 필요하다면, 외부에서 데이터를 인자로 넘겨주는 방식으로 컴포넌트를 수정하자.

 

(2) 데이터 타입을 명시하자

  • 만약 스토리에 Button 컴포넌트가 있고, 이 컴포넌트가 size값에 따라 형태가 달라진다고 가정해보자.

// stories/Button.stories.js

export default {
  title: 'Example/Button',
  component: MyButton,
  argTypes: {
    ...
    size: 'small',  // 'small', 'medium', 'large'
  },
};

 

  • 그런데 만약 개발자가 size를 컨트롤할 때 String값을 입력하게 한다면, 의도된 렌더링을 할 수 없다ㅠ

 

  • 이 경우, 의도된 데이터대로 값을 조작해야 한다면, 옵션 형태로 제공하는 게 좋다.
// stories/Button.stories.js

export default {
  title: "Example/Button",
  component: MyButton,
  argTypes: {
    ...
    size: {
      control: { type: "select" },             // this!
      options: ["small", "medium", "large"],   // this! 
    },
  },
};

 

  • 다행히 스토리북에서는 컨트롤하는 데이터의 타입을 정의할 수 있다 🙂
  • 타입 종류는 여기서 확인하자!

 

(3) 컴포넌트가 재사용성이 떨어지면 안된다.

  • 버튼 컴포넌트에 대한 스토리를 추가하고자 한다.
  • 해당 버튼은 A 서비스에서 쓰일 때는 버튼 상단에 20px 여백이 필요하다.
  • 그래서 버튼 컴포넌트를 만들 때 버튼 상단을 20px로 고정해두었다.
<!-- Button.vue -->
<button type="button" class="simple-btn" @click="onClick">
  {{ label }}
</button>

<style lang="scss" scoped>
.simple-btn {
  margin-top: 20px;   /* 상단 위치 고정 지정 */
}
</style>

 

💡 하지만 B 서비스에서 쓰일 때는 버튼 상단 여백이 10px이어야 한다면?

  • B 서비스용 버튼 컴포넌트를 만들어야 할까? 아니다.
  • 각 서비스 별로 버튼 컴포넌트의 상단 여백 값이 달라져야 한다면, 상단 여백값을 컨트롤할 수 있게 정의해야 한다.
<!-- Button.vue -->
<button type="button" class="simple-btn" @click="onClick" :style="props.style">
  {{ label }}
</button>

<script>
const props = defineProps({    
  style: {       // this!
    type: Object,
    default: () => { marginTop: '20px' }
  }
})
</script>
  • 이처럼 스토리에 추가되는 컴포넌트의 경우, 재사용성을 가지는지 체크하는 게 좋다.




💡 이번 시간에는 스토리북에 대해 알아보았다.
스토리북은 재사용 가능한 UI 컴포넌트에 대한 동작을 미리 볼 수 있는 툴로서,
개발자와 디자이너간의 소통을 원활히 해준다.
환경 설정의 경우 큰 어려움없이 가능하다.


단, 도입시 UI 컴포넌트에 대해 팀과 팀의 소통과 협의 과정이 중요하다고 생각된다.

반응형

댓글