Storybook이란?
Storybook은 UI 구성 요소(컴포넌트)를 개발하기위한 오픈 소스 도구이다.
React나 Vue를 비롯한 컴포넌트 기반 UI 라이브러리들이 많아진 현대의 웹 Front개발 생태계에서는 컴포넌트 하나 하나의 역할이 매우 중요해졌다.
여기서 컴포넌트는 외부에 영향을 받지 않게 Isolated 되어야 한다.
그러기 위해서 Atomic Design Pattern이라는 것이 등장하였고 그에 따라서 UI 자체의 렌더링에도 테스트를 신경쓸 수 있게 되었다.
Storybook은 사실 테스트에만 국한된 라이브러리는 아니고 개발과 이해관계자들 사이에서의 소통과 Docs 및 Props Test를 진행해주는 컴포넌트 기반 오픈 소스 라이브러리이다.
우리는 이 Storybook을 통해서 TDD를 진행하려 한다.
TDD란?
TDD는 Test Driven Development의 줄임말로 TDD는 다음과 같은 순서를 갖게 되고 해당 3가지 사이클을 돌며 개발이 진행된다.
- Red
- Green
- Refactor
그리고 각 사이클에서 우리는 storybook을 활용하여 렌더링을 테스트할 예정이다.
Red
Red Stage 에서는 실패하는 코드를 작성한다.
첫 번째 Red 사이클에서 우리는 Storybook에 아무것도 없는 빈 컴포넌트를 렌더링 할 것이다.
원래라면 해당 story에서 Rendering 이 되어야 할 것이 제대로 나오지 않을 것이다.
Green
Green Stage 에서는 실패한 코드를 토대로 실제 작동하는 코드를 작성한다.
빈 컴포넌트를 렌더링했던 Red Stage에서 더 확장해 실제 우리가 예상하는 기능이 동작하는 컴포넌트를 만들 것이다.
Refactor
Refactor Stage 에서는 Green 에서 동작하는 코드를 더욱 효율적이게 동작하게 한다.
Green Stage에서 넘어온 코드를 짧게 리팩토링을 진행한다.
해당 Stage에서는 마틴 파울러가 말하는 쓰래기 줍기 리팩토링과 비슷한 느낌으로 리팩토링을 진행한다.
왜?
왜 TDD를 작성하면서 개발을 할까?
우선 코드를 작성 할 때 코드가 너무 방대해지지 않는다.
TDD 를 진행하면서 테스트 케이스를 작성할때 주로 작은 단위로 만들기 때문에 코드의 모듈화가 이루어진다.
그럼 그에 따라 당연하게 Test Coverage가 높아지며 안정적인 프로젝트가 구성된다.
테스트 커버리지가 높아지면 결국 리팩토링도 쉬워지고 유지보수도 쉬워진다.
이를 더욱 자세하기 이해하고싶다면 마틴 마울러가 말하는 리팩토링을 해야하는 이유에 대해서 확인해보도록 하자.
TDD에서 SDD로 확장
TDD는 Test Driven Development의 줄임말이라고 앞서 이야기를 하였다.
여기서 더욱 확장시켜 Storybook Driven Development로 활용할 수 있다.
우리는 UI 테스트를 Storybook을 통해서 진행할 것이니 Red, Green Cycle을 모두 Storybook을 통해서 진행한다.
실제로 적용해보자.
이번 예제에서는 Atomic Design Pattern을 적용하여 Label과 Title를 TDD로 진행해볼 것이다.
- Title Component : 제목과 부제목을 위한 컴포넌트로 font-size는 2rem과 1.5rem으로 구성된다.
- Label Component : 설명과 Name Tag, 말꼬리 등과 같은 Text Field를 위한 컴포넌트로
span
태그와p
태그로 구성된다.
프로젝트 Set up
- next.js 설치
- npm modules install
- storybook init
.storybook
폴더 정리
next.js 설치
// npm install
$ npx create-next-app client
// yarn install
$ yarn create-next-app client
npm modules install 설치
우리는 SDD를 위해 Css-In-Js 모듈을 사용하려한다.
styled-components를 설치하자.
// npm 사용자
$ npm install styled-components styled-reset styled-tools
// yarn 사용자
$ yarn add styled-components styled-reset styled-tools
storybook 설치
// npm 사용자
$ npx sb init
// yarn 사용자
$ yarn sb init
storybook 실행
// npm 사용자
$ npm run storybook
// yarn 사용자
$ yarn storybook
.storybook
폴더 정리
.storybook이 storybook을 관리하는 패키지이다.
해당 패키지는 폴더 구조만 우리의 next app 내부에 존재하며 실제로는 완전히 별개로 돌아간다.
그래서 theme.js나 reset.js 와 같은 설정 파일도 storybook에 따로 적용시켜야 한다.
이를 위해서 대부분 webpack.config.js
등과 같이 설정을 하지만 우선 포커스는 SDD 이므로 .storybook
폴더 내부에서 js 파일을 불러오는 방식으로 진행할 것이다.
main.js
module.exports = {
"stories": [
"../components/atoms/**/*.stories.js",
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
]
}
preview.js
import React from "react";
import { ThemeProvider } from 'styled-components';
import { MINIMAL_VIEWPORTS } from "@storybook/addon-viewport";
import theme from "./theme";
import Reset from "./reset";
export const decorators = [
(Story) => (
<>
<Reset />
<ThemeProvider theme={theme}>
<Story />
</ThemeProvider>
</>
),
];
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
viewport: {
viewports: MINIMAL_VIEWPORTS,
},
}
reset.js
import { createGlobalStyle } from "styled-components";
import reset from "styled-reset";
const GlobalStyles = createGlobalStyle`
${reset}
a{
text-decoration: none;
color: inherit;
}
*{
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 14px;
background-color: #000;
color: white;
}
`
export default GlobalStyles;
theme.js
const theme = {
fontSize: {
MainTitle: "2rem;",
SubTitle: "1.5rem;",
PrimaryLabel: "0.9rem;",
SecondaryLabel: "0.8rem;",
PrimaryDescription: "0.7rem;",
SecondaryDescription: "0.6rem;"
}
}
export default theme;
TDD 아닌 SDD 진행하기
우린 2개의 atoms를 TDD하려 한다.
- Title
- Label
우선 components/atoms
폴더를 만든다.
그리고 다음과 같이 2개의 폴더를 만든다.
components/atoms/Title
- index.js
- index.stories.js
- styles.js
components/atoms/Label
- index.js
- index.stories.js
- styles.js
index.js
해당 파일에서 컴포넌트가 정의될 것이다.
일반적인 React 컴포넌트인데, 조건부 렌더링을 통해서 Atomic 한 컴포넌트를 만들자.
index.stories.js
우리의 컴포넌트가 잘 렌더링 되는지, props를 잘 받는지 테스트할 story가 들어있는 폴더이다.
styles.js
우리가 만든 컴포넌트에 스타일링을 적용할 Css-In-Js 파일이다.
Title Components 작업.
Red 사이클
우선 Title 컴포넌트에서 Red 사이클을 돌려보자.
index.js
import React from 'react'
const Title = ({styleType, children}) => {
return (
<div>
this is title;
</div>
)
}
export default Title;
index.stories.js
import Title from ".";
export default {
title: "atoms / Title",
component: Title
}
const Template = (args) => <Title {...args} />
styles.js
import styled, { css } from 'styled-components';
import { theme } from "styled-tools";
export const MainTitle = styled.h1`
`;
export const SubTitle = styled.h3`
`;
Green 사이클
실질적으로 동작하는 컴포넌트를 만들것이다.
Green 사이클에서의 목적은 동작이다.
어떻게 효율적으로 동작하게 할까?는 아직 생각하지 말자.
index.js
import React from 'react'
import * as S from "./styles";
const Title = ({ styleType, children }) => {
if (styleType === "MainTitle") return <S.MainTitle>{children}</S.MainTitle>
else (styleType === "SubTitle") return <S.SubTitle>{children}</S.SubTitle>
}
export default Title;
index.stories.js
import Title from ".";
export default {
title: "atoms / Title",
component: Title
}
export const MainTitleRendering = <Title styleType="MainTitle">This is MainTitle</Title>
export const SubTitleRendering = <Title styleType="SubTitle">This is SubTitle</Title>
styles.js
import styled, { css } from 'styled-components';
import { theme } from "styled-tools";
export const MainTitle = styled.h1`
font-weight: bold;
font-size: 2rem;
`;
export const SubTitle = styled.h3`
font-weight: bold;
font-size: 1.5rem;
`;
Blue (Refactor) 사이클
이제 중복된 코드도 지우고 좀 더 이쁘게 리팩토링을 진행해보자.
이젠 효율을 생각할 때 이다.
index.js
import React from 'react'
import * as S from "./styles";
const Title = ({ styleType, children }) => {
if (styleType === "MainTitle") return <S.MainTitle>{children}</S.MainTitle>
else if (styleType === "SubTitle") return <S.SubTitle>{children}</S.SubTitle>
else retur <></>
}
export default Title;
index.stories.js
import Title from ".";
export default {
title: "atoms / Title",
component: Title
}
const Template = (args) => <Title {...args} />
export const MainTitleRendering = Template.bind({});
MainTitleRendering.args = {
styleType: "MainTitle",
children: "This is Main Ttitle",
}
export const SubTitleRendering = Template.bind({});
SubTitleRendering.args = {
styleType: "SubTitle",
children: "This is Sub Title",
}
styles.js
import styled, { css } from 'styled-components';
import { theme } from "styled-tools";
const defaultStyle = css`
font-weight: bold;
`;
export const MainTitle = styled.h1`
${defaultStyle}
font-size: ${theme("fontSize.MainTitle")};
`;
export const SubTitle = styled.h3`
${defaultStyle}
font-size: ${theme("fontSize.SubTitle")};
`;
Refactoring 후에도 동일하게 잘 동작하는 것을 볼 수 있다.
이제 똑같이 Label 컴포넌트를 만들 차례이다.
Label은 여러분이 한 번 위와같은 과정에 거쳐서 만들어보길 바란다.
'📺 Front End > -- react & redux & nextjs' 카테고리의 다른 글
[React-redux] 늘 그렇듯 Todo List 를 만들어보며 배우는 리액트 리덕스 (0) | 2021.06.10 |
---|---|
[Next.js] _document.js로 레이아웃 템플릿을 사용해서 html 과 body 태그 커스터마이징 하기. (0) | 2021.01.03 |
[Next.js] renderPage 함수로 styled-components 오류 방지하기. (0) | 2021.01.03 |
[styled-components] ThemeProvider에서 MediaQuery를 적용하는 2가지 방법 (2) | 2021.01.02 |
[styled-components] ThemeProvider로 공통 스타일 속성 관리하기. (0) | 2021.01.02 |
댓글2