返回可视化测试手册
React
  • 引言
  • 组件探索器
  • 工作流程
  • 可视化 TDD
  • 自动化
  • 结论

可视化 TDD

编写你的第一个可视化测试

基础知识介绍完毕,现在让我们深入细节。本示例将展示如何使用 Storybook 通过 可视化 TDD 构建 CommentList 组件的各种状态。

  1. 构建可视化测试用例
  2. 在 Storybook 中检查测试
  3. 构建实现
  4. 对照设计稿检查实现
  5. 迭代

我们正在构建什么

CommentList 是一个用于星际自由战士的聊天工具的一部分。我们的设计师根据数据和应用状态为评论列表的各种外观提供了设计稿。我们的任务是确保该列表在精确的文本、显示的图像和视觉处理方面正确渲染。

Commentlist design spec

1. 构建可视化测试用例

通过构建测试用例来启动可视化 TDD。我们将创建三个与上面三张图片相匹配的用例。严格的 TDD 实践者会说我们需要一次开发和实现一个测试用例;这取决于你是否认为这对你的流程有帮助。

让我们使用 degit 设置示例项目,以下载必要的样板模板(带有部分默认配置的已构建应用程序)。运行以下命令

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

cd commentlist

# Install dependencies
yarn

接下来,我们将构建最简单的 CommentList 实现,以确保我们的测试设置正确。

在你的 src 目录中,创建一个名为 components 的新文件夹,然后创建一个名为 CommentList.tsx 的新文件,其内容如下

复制
src/components/CommentList.tsx
interface Author {
  name: string;
  avatar: string;
}

interface Comment {
  text: string;
  author: Author;
}

export interface CommentListProps {
  /**
   * Is the component in the loading state
   */
  loading?: boolean;

  /**
   * Total number of comments
   */
  totalCount?: number;

  /**
   * List of comments
   */
  comments?: Comment[];
}

/**
* The Commentlist component should display the comments from the users.
*/
export default function CommentList({
  loading = false,
  comments = [],
  totalCount = 10,
}: CommentListProps) {
  if (loading) {
    return <div>loading</div>;
  }
  if (comments.length === 0) {
    return <div>empty</div>;
  }
  return (
    <div>
      {comments.length} of {totalCount}
    </div>
  );
}

现在我们有了一个基本实现,可以构建我们的测试状态了。Storybook 使这变得快速简便。

src/components 中创建一个名为 CommentList.stories.ts 的新文件,并添加以下内容

复制
src/components/CommentList.stories.ts
import type { Meta, StoryObj } from '@storybook/react';

import CommentList from './CommentList';

const meta = {
  component: CommentList,
  title: 'CommentList',
} satisfies Meta<typeof CommentList>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Paginated: Story = {
  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: Story = {
  args: {
    comments: [...(Paginated?.args?.comments?.slice(0, 3) || [])],
    totalCount: 3,
  },
};

export const Loading: Story = {
  args: {
    comments: [],
    loading: true,
  },
};

export const Empty: Story = {
  args: {
    ...Loading.args,
    loading: false,
  },
};

2. 在 Storybook 中检查测试

启动 Storybook 查看测试用例。我们的组件实现很简单,但这足以让我们确认测试用例是否按预期渲染。

复制
yarn storybook

3. 构建实现

到目前为止,我们已经搭建了一个基本的实现,然后设置 Storybook 来渲染我们的测试用例。是时候开始独立构建 HasData 变体的实现了。

我们使用 styled-components – 一个在组件级别封装 CSS 的库。运行以下命令

复制
yarn add styled-components

将你的 CommentList.tsx 文件更新为以下内容

复制
src/components/CommentList.tsx
+ import styled, { createGlobalStyle } from 'styled-components';

interface Author {
  name: string;
  avatar: string;
}

interface Comment {
  text: string;
  author: Author;
}

export interface CommentListProps {
  /**
   * Is the component in the loading state
   */
  loading?: boolean;

  /**
   * Total number of comments
   */
  totalCount?: number;

  /**
   * List of comments
   */
  comments?: Comment[];
}

+ const CommentListWrapper = styled.div`
+   font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+   color: #333;
+   display: inline-block;
+   vertical-align: top;
+   width: 265px;
+ `;

+ const CommentItem = 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 Avatar = 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 Message = styled.div`
+   overflow: hidden;
+   padding-top: 10px;
+   padding-right: 20px;
+ `;

+ const Author = styled.span`
+   font-weight: bold;
+ `;

+ const CommentText = styled.span``;

+ const GlobalStyle = createGlobalStyle`
+   @import url('https://fonts.googleapis.com/css?family=Nunito+Sans:400,400i,800');
+ `;

/**
 * The Commentlist component should display the comments from the user.
*/
export default function CommentList({
  loading = false,
  comments = [],
  totalCount = 10,
}: CommentListProps) {
  if (loading) {
    return <div>loading</div>;
  }
  if (comments.length === 0) {
    return <div>empty</div>;
  }
  return (
+   <>
+     <GlobalStyle />
+     <CommentListWrapper>
+       {comments.map(({ text, author: { name, avatar } }) => (
+         <CommentItem key={`comment_${name}`}>
+           <Avatar>
+             <AvatarImg src={avatar} />
+           </Avatar>
+           <Message>
+             <Author>{name}</Author> <CommentText>{text}</CommentText>
+           </Message>
+         </CommentItem>
+       ))}
+     </CommentListWrapper>
+   </>
  );
}

4. 对照设计稿检查实现

在 Storybook 中检查组件的外观。本示例已经提供了 CSS,但在实践中,我们会随着进度调整样式并在 Storybook 中确认它们。

5. 迭代

如果我们对步骤 4 中的实现不满意,我们会回到步骤 3 继续修改。如果 UI 与规范匹配,那么我们将继续构建下一个变体——例如向 Paginated story 中添加“加载更多”按钮。

在这个工作流程中迭代时,定期检查每个 story,以确保最终实现正确处理了每个测试状态,而不仅仅是我们最后修改的那个。

学习如何自动化可视化测试

在下一章中,我们将看到如何使用 Chromatic 自动化 VTDD 流程,Chromatic 是一个由 Storybook 维护者创建的免费可视化测试服务。

将你的代码与本章节保持同步。在 GitHub 查看 bbdb86d。
这份免费指南对你有帮助吗?发推文点赞并帮助其他开发者找到它。
下一章
自动化
自动化可视化测试以捕获回归
✍️ 在 GitHub 上编辑 – 欢迎 PR!
加入社区
6,975开发者及更多
原因为什么选择 Storybook组件驱动的 UI
开源软件
Storybook - Storybook 中文

特别鸣谢 Netlify CircleCI