返回可视化测试手册
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,721位开发者及更多
为什么为什么选择 Storybook组件驱动的 UI
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI