Skip to main content

One post tagged with "storybook"

View All Tags

비주얼 테스팅 핸드북 (국을버녁+)

원본: https://storybook.js.org/tutorials/visual-testing-handbook

Introduction to visual testing#

The pragmatic way to test user interfaces

사용자 인터페이스는 주관적입니다. "이거 맞나요?" 에 대한 답은 브라우저, 장치 및 개인 취향에 따라 다릅니다. 여전히 렌더링된 UI를 보고 모양을 확인해야 합니다.

그러나 커밋할 때마다 전체 UI를 수동으로 확인하는 데 시간이 오래 걸립니다. 단위 및 스냅샷 테스트와 같은 다양한 접근 방식은 시각적 검증을 자동화하려고 시도합니다. 기계가 HTML 태그 및 CSS 클래스의 시퀀스에서 UI 정확성을 결정할 수 없기 때문에 종종 실패로 끝납니다.

팀들은 시각적 버그를 방지하기 위해 어떻게 할까요? Microsoft, BBC 및 Shopify는 수백만 명의 사용자에게 UI를 제공하기 위해 어떤 기술을 사용합니까? 공동 저자인 Tom과 저는 주요 팀을 조사하여 실제로 효과가 있는 것이 무엇인지 알아냈습니다.

이 핸드북은 인간의 눈의 정확성과 기계의 효율성을 결합한 실용적인 접근 방식인 시각적 테스트를 소개합니다. 테스트 방정식에서 사람을 제거하는 대신 시각적 테스트는 도구를 사용하여 주의가 필요한 특정 UI 변경에 집중합니다.

Visual testing driven path

Unit tests don't have eyeballs#

시각적 테스트를 이해하려면 단위 테스트부터 시작하는 것이 좋습니다. 최신 UI는 구성 요소 기반이며 모듈식 부분으로 구성됩니다. 구성 요소 구성을 사용하면 UI를 props 및 state의 함수로 렌더링할 수 있습니다. 즉, 다른 기능과 마찬가지로 구성 요소를 단위 테스트할 수 있습니다.

단위 테스트는 모듈을 분리한 다음 동작을 확인합니다. 입력(속성, 상태 등)을 제공하고 출력을 예상 결과와 비교합니다. 단위 테스트는 개별적으로 모듈을 테스트하면 엣지 케이스를 커버하고 실패의 원인을 정확히 찾아내는 것이 더 쉽기 때문에 바람직합니다.

핵심 문제는 UI의 고유한 복잡성 중 상당 부분이 시각적이라는 것입니다. 생성된 HTML 및 CSS가 사용자 화면에서 어떻게 렌더링되는지에 대한 세부 사항입니다.

단위 테스트는 2 + 2 === 4와 같이 구체적인 출력을 평가하는 데 적합합니다. 그러나 HTML 또는 CSS의 세부 사항이 모양과 어떻게 영향을 미치는지 식별하기 어렵기 때문에 UI에는 적합하지 않습니다. 예를 들어 HTML 변경이 UI 모양과 느낌에 항상 영향을 미치는 것은 아닙니다.

What about snapshot tests?#

Snapshot tests는 UI 모양을 확인하는 대체 접근 방식을 제공합니다. 구성 요소를 렌더링한 다음 생성된 DOM을 "기준선"으로 캡처합니다. 후속 변경은 새 DOM을 기준선과 비교합니다. 차이점이 있는 경우 개발자는 기준선을 명시적으로 업데이트해야 합니다.

실제로 DOM 스냅샷은 HTML blob을 평가하여 UI가 렌더링되는 방식을 결정하기 어렵기 때문에 어색합니다.

스냅샷 테스트는 다른 자동화된 UI 테스트와 마찬가지로 취약합니다. 구성 요소의 내부 작업을 변경하려면 구성 요소의 렌더링된 출력이 변경되었는지 여부에 관계없이 테스트를 업데이트해야 합니다.

Visual testing is made for UIs#

시각적 테스트는 UI 모양의 변경 사항을 포착하도록 설계되었습니다. Storybook과 같은 구성 요소 탐색기를 사용하여 UI 구성 요소를 격리하고 변형을 조롱하고 지원되는 테스트 사례를 "이야기"로 저장합니다.

개발하는 동안 브라우저에서 구성 요소를 렌더링하여 구성 요소의 모양을 확인하여 구성 요소의 빠른 수동 확인을 "실행"합니다. 구성 요소 탐색기에 나열된 각 테스트 사례를 전환하여 구성 요소의 변형을 확인합니다.

QA에서 자동화를 사용하여 회귀를 감지하고 UI 일관성을 적용합니다. Chromatic과 같은 도구는 일관된 브라우저 환경에서 마크업, 스타일 지정 및 기타 자산으로 완성된 각 테스트 사례의 이미지 스냅샷을 캡처합니다.

커밋할 때마다 새 이미지 스냅샷이 이전에 승인된 기준 스냅샷과 자동으로 비교됩니다. 기계가 시각적 차이를 감지하면 개발자는 의도적인 변경을 승인하거나 우발적인 버그를 수정하라는 알림을 받습니다.

일이 많아보이네요...

힘들게 들릴 수 있지만 자동화된 테스트에서 오탐지를 선별하고 사소한 UI 변경 사항과 일치하도록 테스트 케이스를 업데이트하고 테스트를 다시 통과하기 위해 초과 근무를 하는 것보다 쉽습니다.

Learn the tooling#

이제 시각적 테스트에 대한 감각이 생겼으므로 이를 활성화하는 데 필요한 주요 도구인 구성 요소 탐색기를 확인해 보겠습니다. 다음 장에서는 구성 요소 탐색기가 개발자가 구성 요소를 빌드하고 테스트하는데 어떻게 도움이 되는지 알아보겠습니다.

Component explorers#

UI 개발 및 시각적 테스트를 위한 도구

최신 UI는 상태, 언어, 장치, 브라우저 및 사용자 데이터의 무수한 순열을 지원합니다. 과거에는 UI 개발이 번거로웠습니다. 적절한 설정을 사용하여 올바른 장치에서 주어진 페이지로 이동해야 합니다. 그런 다음 주변을 클릭하여 페이지를 올바른 상태로 전환하여 코딩을 시작할 수 있습니다.

구성 요소 탐색기는 비즈니스 논리 및 앱 컨텍스트에서 UI 문제를 분리합니다. 각 구성 요소의 지원되는 변형에 초점을 맞추기 위해 UI 구성 요소를 별도로 빌드합니다. 이를 통해 입력(속성, 상태)이 렌더링된 UI에 어떻게 영향을 미치고 시각적 테스트 모음의 기초를 형성하는지 측정할 수 있습니다.

Storybook은 시각적 테스트를 시연하는데 사용할 업계 표준 구성 요소 탐색기입니다. Twitter, Slack, Airbnb, Shopify, Stripe 및 수천 개의 다른 회사에서 채택하고 있으므로 어디에서 일하든 이 가이드의 학습 내용을 적용할 수 있습니다.

Why build UIs in isolation?#

Fewer bugs#

구성 요소와 상태가 많을수록 사용자의 장치와 브라우저에서 모두 올바르게 렌더링되는지 확인하기가 더 어렵습니다.

구성 요소 탐색기는 구성 요소의 지원되는 변형을 표시하여 불일치를 방지합니다. 이를 통해 개발자는 각 상태에 독립적으로 집중할 수 있습니다. 개별적으로 테스트할 수 있으며 조롱을 사용하여 복잡한 엣지 케이스를 복제할 수 있습니다.

Component test cases

Faster development#

앱은 결코 끝나지 않습니다. 지속적으로 반복합니다. 따라서 UI 아키텍처는 새로운 기능을 수용할 수 있어야 합니다. 구성 요소 모델은 UI를 애플리케이션 비즈니스 로직 및 백엔드와 분리하여 호환성을 장려합니다.

구성 요소 탐색기는 샌드박스를 제공하여 앱에서 격리된 상태로 UI를 개발함으로써 이러한 구분을 명확하게 합니다. 즉, 팀은 앱의 다른 부분에서 주의를 산만하게 하거나 오염을 주지 않고 다른 UI 부분에서 동시에 작업할 수 있습니다.

Easier collaboration#

UI는 본질적으로 시각적입니다. 코드 전용 pull 요청은 작업의 불완전한 표현입니다. 진정한 협업을 실현하려면 이해 관계자가 UI를 살펴보아야 합니다.

구성 요소 탐색기는 UI 구성 요소와 모든 변형을 시각화합니다. 이렇게 하면 개발자, 디자이너, 제품 관리자 및 QA 로부터 "이게 맞습니까?"에 대한 피드백을 쉽게 얻을 수 있습니다.

Where does it fit into my tech stack?#

구성 요소 탐색기는 앱과 함께 제공되는 작은 독립 실행형 샌드박스로 패키지됩니다. 구성 요소 변형을 개별적으로 시각화할 수 있으며 아래 기능이 포함되어 있습니다.

  • 🧱 구성 요소 격리를 위한 샌드박스
  • 🔭 구성 요소 사양 및 속성에 대한 변형 시각화 도구
  • 🧩 테스트 중에 다시 방문할 수 있도록 변형을 "스토리"로 저장
  • 📑 구성 요소 검색 및 사용 지침에 대한 문서

Relation between components and component explorers

Learn the workflow#

구성 요소 탐색기로 UI를 분리하면 시각적 테스트가 해제됩니다. 다음 장에서는 UI 개발을 위해 테스트 주도 개발을 리믹스하는 방법을 보여줍니다.

Workflow#

구성 요소 구축을 위한 테스트 기반 워크플로

사용자 인터페이스 개발은 항상 잘못 정의되었습니다. UI의 주관적인 특성은 임시 개발 워크플로와 버그가 있는 UI로 이어집니다. 이 장에서는 전문 팀이 엄격한 시각적 테스트 기반 방식으로 UI를 구축하는 방법을 공유합니다.

Test-driven development#

시작하기 전에 인기 있는 엔지니어링 방식인 테스트 주도 개발 test-driven development (TDD)를 요약해 보겠습니다. TDD의 핵심 아이디어는 테스트 중인 기능을 개발하기 전에 테스트를 작성한다는 것입니다.

  1. 코드에 대한 자동화된 단위 테스트 세트 구성
  2. "테스트를 녹색으로 전환"하는 코드 자체를 작성

TDD를 사용하면 구체적인 입력 측면에서 코드가 수행해야 하는 작업에 대해 명확하게 생각할 수 있습니다(구성 요소의 경우 이를 "상태"라고 함). 그렇게 하면 모듈의 모든 사용 사례를 다룰 수 있습니다.

예를 들어 보겠습니다. 원시 날짜 개체를 "2주 전" 형식의 상대 날짜 형식으로 변환하는 상대화 함수가 있다고 가정합니다. 다루고자 하는 다양한 유형의 입력을 모두 요약하는 것은 매우 간단합니다. 그런 다음 솔루션을 향한 진전이 있다고 생각할 때마다 "테스트" 버튼을 누르십시오.

테스트 프레임워크를 사용하면 해당 부분을 테스트하기 위해 전체 애플리케이션에 대한 입력을 제공할 필요 없이 relativize 기능을 분리하여 실행할 수 있습니다.

그러나 테스트를 미리 정의하기 어렵고 모듈을 분리하기 어렵고 출력이 주관적이기 때문에 UI를 개발할 때 TDD가 떨어집니다. 이러한 단점은 시각적 테스트 구성 요소를 격리하여 해결합니다.

Visual testing#

UI 테스트의 까다로운 부분은 코드만으로는 관련 시각적 세부 정보를 확인할 수 없다는 것입니다. 시각적 테스트는 빠르고 집중적인 방식으로 인간의 판단을 포함하여 이를 우회합니다.

Visual testing workflow#

실제로 시각적 테스트는 Storybook을 사용하여 정의된 테스트 상태 집합에서 구성 요소를 "시각적으로" 테스트합니다. 시각적 테스트는 다른 유형의 테스트와 동일한 설정, 실행 및 분해 단계를 공유하지만 확인 단계는 사용자에게 있습니다.

test do
setup
execute 👈 Storybook renders stories
verify 👈 you look at stories
teardown
end

그런 다음 이미지 스냅샷을 자동으로 캡처하고 비교하여 회귀를 포착합니다.

test do
setup
execute 👈 Storybook renders stories
verify 👈 capture image snapshots and compare them to baselines
teardown
end

두 시나리오에서 동일한 테스트 케이스가 사용되며 검증 방법만 변경됩니다.

How to write visual test cases#

지금은 첫 번째 시나리오에 집중하겠습니다. Storybook에서 테스트는 React 요소를 렌더링하는 것만큼 간단합니다. Storybook 용어로 "이야기"인 시각적 테스트 사례를 작성하기 위해 관심 있는 구성 요소의 상태를 간략하게 설명합니다. 아래 코드 샘플은 InboxTask, SnoozzedTaskPinnedTask에 대한 시각적 테스트를 작성하는 방법을 보여줍니다.

src/components/Task.stories.js
import React from 'react';
import Task from './Task';
export default {
component: Task,
title: 'Task',
};
const Template = args => <Task {...args} />;
export const InboxTask = Template.bind({});
InboxTask.args = {
task: {
id: '1',
title: 'Test Task',
state: 'TASK_INBOX',
updatedAt: new Date(2021, 0, 1, 9, 0),
boardName: 'on Test Board',
},
};
export const SnoozedTask = Template.bind({});
SnoozedTask.args = {
task: {
// Shaping the stories through args composition.
...InboxTask.args.task,
state: 'TASK_SNOOZED',
},
};
export const PinnedTask = Template.bind({});
PinnedTask.args = {
task: {
// Shaping the stories through args composition.
...InboxTask.args.task,
state: 'TASK_PINNED',
},
};

Storybook에서는 작업과 그 변형이 사이드바에 나타납니다. 이것은 테스트 주기의 "실행" 단계에 해당합니다. Storybook에서 눈으로 확인하는 "확인" 단계

UI 테스트의 경우 사람의 확인은 시각적인 모양에 영향을 주지 않는 구성 요소의 변경 사항에 대해 강력하기 때문에 실용적인 접근 방식입니다. 또한 미리 입력을 작성하고 출력을 시각적으로 확인하기만 하면 되므로 TDD 스타일로 UI를 자동으로 빌드합니다.

Learn visual test-driven development#

신중한 디자인으로 앱을 빌드하는 경우 디자인 아티팩트에 입력 및 출력이 포함된 잘 지정된 구성 요소 집합이 있을 가능성이 있습니다. 이 "디자인 사양"을 시각적 테스트 프로세스와 연결하면 TDD와 정확히 유추할 수 있습니다.

다음 장에서는 Visual TDD를 사용하여 예제 구성 요소를 코딩하여 지금까지 배운 내용을 적용합니다.

Visual TDD#

Write your first visual tests#

이제 기본 사항을 다루었으므로 세부 사항으로 넘어갑니다. 이 예제는 Storybook과 함께 Visual TDD를 사용하여 CommentList 구성 요소의 상태를 구축하는 방법을 보여줍니다.

  1. 시각적 테스트 케이스 구축
  2. Storybook에서 테스트 확인
  3. 구현 구축
  4. 디자인에 대한 구현 확인
  5. 반복

What we're building#

CommentList는 은하계 독립투사를 위한 채팅 도구의 일부입니다. 우리 디자이너는 데이터와 앱 상태를 기반으로 댓글 목록이 표시되어야 하는 다양한 방식에 대한 디자인을 제공했습니다. 우리의 임무는 정확한 텍스트, 표시된 이미지 및 시각적 처리 측면에서 목록이 올바르게 렌더링되도록 하는 것입니다.

Commentlist design spec

1. Build visual test cases#

테스트 케이스를 구축하여 시각적 TDD를 시작하십시오. 위의 세 이미지와 일치하는 세 가지 케이스를 만들 것입니다. 엄격한 TDD 전문가는 한 번에 하나의 테스트 케이스를 개발하고 구현해야 한다고 말할 것입니다. 이것이 귀하의 프로세스에 도움이 된다고 생각하는지 여부는 귀하에게 달려 있습니다.

Let's set up the example project using degit to download the necessary boilerplate templates (partially built applications with some default configuration). Run the following commands:

필요한 상용구 템플릿(일부 기본 구성으로 부분적으로 빌드된 응용 프로그램)을 다운로드하기 위해 degit을 사용하여 예제 프로젝트를 설정해 보겠습니다. 다음 명령을 실행합니다.

# Clone the template for this tutorial
npx degit chromaui/visual-testing-handbook-react-template commentlist
cd commentlist
# Install dependencies
yarn

Next, we’ll build the simplest-possible CommentList implementation so that we can ensure our tests are set up correctly.

다음으로 테스트가 올바르게 설정되었는지 확인할 수 있도록 가장 간단한 CommentList 구현을 빌드합니다.

src/components/CommentList.js
import React from 'react';
import PropTypes from 'prop-types';
export default function CommentList({ loading, comments, totalCount }) {
if (loading) {
return <div>loading</div>;
}
if (comments.length === 0) {
return <div>empty</div>;
}
return (
<div>
{comments.length} of {totalCount}
</div>
);
}
CommentList.propTypes = {
/**
* Is the component in the loading state
*/
loading: PropTypes.bool,
/**
* Total number of comments
*/
totalCount: PropTypes.number,
/**
* List of comments
*/
comments: PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string,
author: PropTypes.shape({
name: PropTypes.string,
avatar: PropTypes.string,
}),
})
),
};
CommentList.defaultProps = {
loading: false,
totalCount: 10,
comments: [],
};

이제 기본 구현이 있으므로 테스트 상태를 빌드할 수 있습니다. Storybook을 사용하면 이를 빠르고 쉽게 수행할 수 있습니다.

src/components에 CommentList.stories.js라는 새 파일을 만들고 다음을 추가합니다.

src/components/CommentList.stories.js
import React from 'react';
import CommentList from './CommentList';
export default {
component: CommentList,
title: 'CommentList',
};
const Template = args => <CommentList {...args} />;
export const Paginated = Template.bind({});
Paginated.args = {
comments: [
{
text: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.',
author: {
name: 'Luke',
avatar: 'luke.jpeg',
},
},
{
text: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco.',
author: {
name: 'Leah',
avatar: 'leah.jpeg',
},
},
{
text: 'Duis aute irure dolor in reprehenderit in voluptate.',
author: {
name: 'Han',
avatar: 'han.jpeg',
},
},
{
text: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco.',
author: {
name: 'Poe',
avatar: 'poe.jpeg',
},
},
{
text: 'Duis aute irure dolor in reprehenderit in voluptate.',
author: {
name: 'Finn',
avatar: 'finn.jpeg',
},
},
],
totalCount: 10,
};
export const HasData = Template.bind({});
HasData.args = {
comments: [...Paginated.args.comments.slice(0, 3)],
totalCount: 3,
};
export const Loading = Template.bind({});
Loading.args = {
comments: [],
loading: true,
};
export const Empty = Template.bind({});
Empty.args = {
...Loading.args,
loading: false,
};

2. Check the tests in Storybook#

테스트케이스를 보려면 Storybook을 시작하세요. 우리의 구성 요소 구현은 뼈대뿐이지만 테스트 케이스가 의도한 대로 렌더링되는지 확인할 수 있습니다.

# Start Storybook in development mode
yarn storybook

3. Build out the implementation#

지금까지 우리는 기본적인 구현을 스캐폴딩한 다음 스토리북을 설정하여 테스트 케이스를 렌더링했습니다. 이제 HasData 변형 구현을 독립적으로 구축하기 시작할 때입니다.

구성 요소 수준에서 CSS를 캡슐화하는 라이브러리인 styled-components를 사용합니다. 다음 명령을 실행합니다.

yarn add styled-components

CommentList.js파일을 아래와 같이 업데이트 합니다.

src/components/CommentList.stories.js
import React from 'react';
import PropTypes from 'prop-types';
+ import styled, { createGlobalStyle } from 'styled-components';
+ const CommentListDiv = styled.div`
+ font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color: #333;
+ display: inline-block;
+ vertical-align: top;
+ width: 265px;
+ `;
+ const CommentItemDiv = styled.div`
+ font-size: 12px;
+ line-height: 14px;
+ clear: both;
+ height: 48px;
+ margin-bottom: 10px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 0 10px 0;
+ background: linear-gradient(
+ 120deg,
+ rgba(248, 248, 254, 0.95),
+ rgba(250, 250, 250, 0.95)
+ );
+ border-radius: 48px;
+ `;
+ const AvatarDiv = styled.div`
+ float: left;
+ position: relative;
+ overflow: hidden;
+ height: 48px;
+ width: 48px;
+ margin-right: 14px;
+ background: #dfecf2;
+ border-radius: 48px;
+ `;
+ const AvatarImg = styled.img`
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ left: 0;
+ top: 0;
+ z-index: 1;
+ background: #999;
+ `;
+ const MessageDiv = styled.div`
+ overflow: hidden;
+ padding-top: 10px;
+ padding-right: 20px;
+ `;
+ const AuthorSpan = styled.span`
+ font-weight: bold;
+ `;
+ const TextSpan = styled.span``;
+ const GlobalStyle = createGlobalStyle`
+ @import url('https://fonts.googleapis.com/css?family=Nunito+Sans:400,400i,800');
+ `;
export default function CommentList({ loading, comments, totalCount }) {
if (loading) {
return <div>loading</div>;
}
if (comments.length === 0) {
return <div>empty</div>;
}
return (
+ <>
+ <GlobalStyle/>
+ <CommentListDiv>
+ {comments.map(({ text, author: { name, avatar } }) => (
+ <CommentItemDiv key={`comment_${name}`}>
+ <AvatarDiv>
+ <AvatarImg src={avatar} />
+ </AvatarDiv>
+ <MessageDiv>
+ <AuthorSpan>{name}</AuthorSpan> <TextSpan>{text}</TextSpan>
+ </MessageDiv>
+ </CommentItemDiv>
+ ))}
+ </CommentListDiv>
+ </>
);
}
CommentList.propTypes = {
/**
* Is the component in the loading state
*/
loading: PropTypes.bool,
/**
* Total number of comments
*/
totalCount: PropTypes.number,
/**
* List of comments
*/
comments: PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string,
author: PropTypes.shape({
name: PropTypes.string,
avatar: PropTypes.string,
}),
})
),
};
CommentList.defaultProps = {
loading: false,
totalCount: 10,
comments: [],
};

4. Check the implementation against the design#

구성 요소가 Storybook에서 어떻게 보이는지 확인하십시오. 이 예제는 CSS를 이미 제공했지만 실제로는 진행하면서 스타일을 조정하고 Storybook에서 확인했습니다.

5. Iterate#

4단계의 구현에 만족하지 않으면 3단계로 돌아가 계속 작업합니다. UI가 사양과 일치하면 페이지가 매겨진 스토리에 "더 로드" 버튼을 추가하여 다음 변형을 빌드하는 단계로 넘어갑니다.

이 워크플로를 반복하면서 각 스토리를 정기적으로 확인하여 최종 구현이 우리가 작업한 마지막 상태뿐만 아니라 각 테스트 상태를 올바르게 처리하는지 확인합니다.

Learn how to automate visual testing#

다음 장에서는 Storybook 운영자들이 만든 무료 시각적 테스트 서비스인 Chromatic을 사용하여 VTDD 프로세스를 자동화하는 방법을 살펴보겠습니다.

Automate visual testing#

Automate visual testing to catch regressions#

자연스러운 개발 과정에서 버그는 불가피합니다. 시각적 테스트 자동화는 사람이 검토할 수 있도록 기계를 사용하여 UI 모양의 변경 사항을 감지합니다.

간단히 말해서 이미지 스냅샷은 모든 구성 요소 변형에 대해 만들어집니다. 이것은 시각적 테스트 "기준선" 역할을 합니다. 커밋할 때마다 새 스냅샷이 캡처되고 픽셀 단위로 기준선과 비교됩니다. UI 변경 사항이 있는 경우 버그인지 의도적인 업데이트인지 검토하라는 알림을 받습니다.

Setup a repository in GitHub#

그런 다음 지침에 따라 저장소를 설정합니다. your-username을 GitHub 계정 이름으로 바꿉니다.

GitHub에서 리포지토리 설정 시작하기 전에 로컬 CommentList 코드를 원격 버전 제어 서비스와 동기화해야 합니다.

Set up comment list repo in GitHub

GitHub로 이동하여 여기에서 프로젝트에 대한 새 리포지토리를 만듭니다. 리포지토리의 이름을 로컬 프로젝트와 동일하게 "commentlist"로 지정합니다.

git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/your-username/commentlist.git
git push -u origin main

Setup Chromatic#

우리는 Chromatic by Storybook 관리자를 사용하여 이미지 스냅샷 프로세스를 시연할 것입니다. chromatic.com으로 이동하여 GitHub 계정으로 가입합니다.

Chromatic sign in

거기에서 방금 생성한 리포지토리를 선택합니다.

UI 테스트는 클라우드 브라우저 환경에서 모든 스토리의 이미지 스냅샷을 캡처합니다. 코드를 푸시할 때마다 Chromatic은 새로운 스냅샷 세트를 생성하고 기준선과 비교합니다. 시각적 변화가 있는 경우 의도적인 것인지 확인합니다.

Establish baselines#

프로젝트에 Chromatic을 개발 패키지로 추가합니다.

yarn add -D chromatic

Chromatic running

설치가 완료되면 필요한 모든 것이 있습니다. 지금이 변경 사항을 커밋하고 원격 리포지토리에 푸시하기에 좋은 시간입니다.

git add .
git commit -m "Added Chromatic"
git push

chromatic 명령으로 스토리북을 만들고 게시하세요. 웹사이트에서 project-token을 하나의 Chromatic 소모품으로 교체하는 것을 잊지 마십시오.

yarn chromatic --project-token=<project-token>

이 하나의 명령으로 스토리북을 게시하고 Chromatic을 트리거하여 각 스토리의 이미지 스냅샷을 캡처하고(표준화된 클라우드 브라우저에서) 스냅샷을 기준으로 설정했습니다.

후속 빌드는 UI 변경을 감지하기 위해 기존 기준과 비교되는 새 스냅샷을 생성합니다.

Baselines in Chromatic

Run tests#

pull 요청에 크든 작든 UI 변경 사항이 포함될 때마다 시각적 테스트를 실행하는 것이 도움이 됩니다. Chromatic은 새 스냅샷을 이전 빌드의 기존 기준선과 비교합니다.

이 개념을 보여주기 위해 UI를 약간 변경해 보겠습니다.

git checkout -b change-commentlist-outline

CommentList 구성 요소 조정

src/components/CommentList.js
import React from 'react';
import PropTypes from 'prop-types';
import styled, { createGlobalStyle } from 'styled-components';
const CommentListDiv = styled.div`
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #333;
display: inline-block;
vertical-align: top;
width: 265px;
`;
const CommentItemDiv = styled.div`
font-size: 12px;
line-height: 14px;
clear: both;
height: 48px;
margin-bottom: 10px;
box-shadow: rgba(0, 0, 0, 0.2) 0 0 10px 0;
background: linear-gradient(
120deg,
rgba(248, 248, 254, 0.95),
rgba(250, 250, 250, 0.95)
);
border-radius: 48px;
+ border: 4px solid red;
+ font-weight: bold;
`;
const AvatarDiv = styled.div`
float: left;
position: relative;
overflow: hidden;
height: 48px;
width: 48px;
margin-right: 14px;
background: #dfecf2;
border-radius: 48px;
`;
const AvatarImg = styled.img`
position: absolute;
height: 100%;
width: 100%;
left: 0;
top: 0;
z-index: 1;
background: #999;
`;
const MessageDiv = styled.div`
overflow: hidden;
padding-top: 10px;
padding-right: 20px;
`;
const AuthorSpan = styled.span`
font-weight: bold;
`;
const TextSpan = styled.span``;
const GlobalStyle = createGlobalStyle`
@import url('https://fonts.googleapis.com/css?family=Nunito+Sans:400,400i,800');
`;
export default function CommentList({ loading, comments, totalCount }) {
if (loading) {
return <div>loading</div>;
}
if (comments.length === 0) {
return <div>empty</div>;
}
return (
<>
<GlobalStyle/>
<CommentListDiv>
{comments.map(({ text, author: { name, avatar } }) => (
<CommentItemDiv key={`comment_${name}`}>
<AvatarDiv>
<AvatarImg src={avatar} />
</AvatarDiv>
<MessageDiv>
<AuthorSpan>{name}</AuthorSpan> <TextSpan>{text}</TextSpan>
</MessageDiv>
</CommentItemDiv>
))}
</CommentListDiv>
</>
);
}
CommentList.propTypes = {
/**
* Is the component in the loading state
*/
loading: PropTypes.bool,
/**
* Total number of comments
*/
totalCount: PropTypes.number,
/**
* List of comments
*/
comments: PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string,
author: PropTypes.shape({
name: PropTypes.string,
avatar: PropTypes.string,
}),
})
),
};
CommentList.defaultProps = {
loading: false,
totalCount: 10,
comments: [],
};

변경 사항을 커밋하고 저장소에 푸시하고 Chromatic을 실행합니다.

git commit -am "make CommentList sparkle"
git push -u origin change-commentlist-outline
yarn chromatic --project-token=<project-token>

GitHub 리포지토리에서 새 분기에 대한 풀 요청을 엽니다.

Comment list pull requested opened in GitHub

검토할 수 있도록 크로매틱 감지된 UI 변경 사항! PR 점검으로 이동하여 "🟡 UI 테스트"를 클릭하여 변경 사항 목록을 확인하십시오. 빌드는 "검토되지 않음"으로 표시되고 변경 사항은 "테스트" 테이블에 나열됩니다.

New changes published to Chromatic

Review changes#

시각적 테스트를 자동화하면 구성 요소가 우연히 변경되지 않도록 합니다. 그러나 변경 사항이 의도적인지 여부를 결정하는 것은 여전히 개발자의 몫입니다.

의도적인 변경인 경우 스냅샷을 수락하여 기준선을 업데이트합니다. 이는 향후 테스트가 빨간색 테두리가 있는 CommentList와 비교될 것임을 의미합니다.

의도하지 않은 변경이라면 수정이 필요합니다. 우리 디자이너는 ✨majestic✨ 빨간색 테두리가 끔찍하다고 생각하므로 취소합시다.

Chromatic test screen

Merge changes#

버그가 수정되고 기준선이 최신 상태가 되면 코드를 대상 분기에 다시 병합할 준비가 된 것입니다. Chromatic은 허용된 기준선을 분기 간에 전송하므로 기준선은 한 번만 수락하면 됩니다.

visual testing workflow

Continuous integration#

변경할 때마다 이 명령을 로컬에서 실행하는 것은 번거롭습니다. 프로덕션 팀은 코드가 CI/CD 파이프라인에 푸시될 때 시각적 테스트 실행을 트리거합니다. 이 튜토리얼에서는 설정하지 않지만 Chromatic's CI docs에서 더 자세히 알아볼 수 있습니다.

Your journey begins#

Visual Testing Handbook은 선도적인 프론트엔드 팀이 UI 모양을 테스트하는 방법을 보여줍니다. UI가 의도한 디자인과 일치하고 시간이 지남에 따라 버그가 없는지 확인하는 실용적인 방법입니다.

이 가이드가 자신의 시각적 테스트 전략에 영감을 주기를 바랍니다. 마지막 장은 전체 샘플 코드와 유용한 리소스로 끝납니다.

Conclusion#

Say bye to visual bugs

개발자는 버그 수정에 21%의 시간을 보냅니다. 디버깅 UI 모양은 특히 실망스러울 수 있습니다. 재생산하려면 다양한 브라우저를 실행하고 앱을 올바른 상태로 만들고 DOM을 통과해야 합니다. 판돈도 더 높습니다. 포착되지 않은 버그는 QA보다 프로덕션에서 수정하는 데 5-10x 더 많은 시간이 소요됩니다.

수천 개의 프론트엔드 팀이 Storybook을 사용하여 시각적 테스트를 수행하는 것은 상식입니다. Storybook은 구성 요소를 빌드하고 시각적 테스트를 작성하는 데 도움이 됩니다. 구성 요소 수준에서 테스트를 실행하면 버그의 근본 원인을 정확히 찾아낼 수 있습니다. 이미지 스냅샷을 찍으면 regressions 회귀를 자동으로 포착하는 데 도움이 됩니다. 이는 사람들이 숨겨진 버그에 대해 걱정하지 않고 UI를 제공할 수 있음을 의미합니다.

이 가이드에서는 시각적 테스트의 기본 사항을 소개했습니다. Tom과 저는 여러분이 자신의 프로젝트에서 이러한 학습을 기반으로 할 수 있기를 바랍니다. 이와 같은 유용한 기사와 가이드에 대한 알림을 받으려면 Storybook 메일링 리스트에 가입하세요.

Sample code for this tutorial#

잘 따라했다면 리포지토리와 배포된 스토리북은 다음과 같아야 합니다.

More resources#

더 깊이 들어가고 싶으십니까? 다음은 몇 가지 유용한 추가 리소스입니다.

  • Official Storybook docs has API documentation, examples, and the addon gallery.
  • How to actually test UIs is a summary of practical UI testing strategies from Shopify, Adobe, Twilio, and more.
  • Discord chat puts you in contact with the Storybook community and maintainers.
  • Blog showcases the latest releases and features to streamline your UI development workflow.