> For the complete documentation index, see [llms.txt](https://jimmylv.gitbook.io/tdd-frontend/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jimmylv.gitbook.io/tdd-frontend/coding/00-project-commentbox/04-quick-feedback-of-components-from-stroybook.md).

# 任务 3：组件级别的快速反馈

Hey，你好，我是 JimmyLv 吕靖，这是你开始 前端 TDD 练习的第 13 天，今天我们会接着练习 CommentBox 项目，学习如何使用 Storybook 来测试组件样式。

## 练习目标

在之前的练习中，我们还没有涉及到组件样式，但其实在前端工作中大部分时间是消耗在了调整 CSS 样式上面，可能你也有过如下“内心崩溃”的经历。

![](/files/-M1jB7M30kTPdW0J4KaV)

这一次，我们对页面及组件的样式有严格的要求，具体的评论框样式可参考 YouTube（需要梯子），或 Ant Design UI 组件库，需要尽可能做到 100% 像素级还原。

```
yarn stroybook #启动组件运行时
yarn test #运行单元测试
yarn test:story #运行组件快照测试
```

## 本次任务的练习要求

* 使用独立的 Storybook 组件运行时，调整样式
* 尽可能穷尽组件的所有使用场景，一种情况一个 story
* 完成所有情况后，使用组件快照测试自动化回归组件样式
* 别忘了重新运行 Cypress E2E 测试，看是否破坏原有功能

## Storybook 组件运行时

> Storybook: UI component explorer for frontend developers

通过 CDD（Components-Driven-Development）的方式在 React Storybook 里边儿写 `components.stories.js`，即人为验证，视觉驱动出来的组件代码，在样式上的反馈，人眼反而是最快的。

![Storybook Cart Example](/files/-M1jB7M5AO-uQARZps0l)

### 验证组件的 UI 样式

```javascript
#Counter.stories.js
export default { title: 'Counter' }
export const enabled = () => (
  <Counter text="Enabled" />
);
export const disabled = () => (
  <Counter disabled text="Disabled" />
);
```

### 在测试中使用 Storybook 用例

这是一个额外的福利，当我们在测试用例中 render 一个组件后，可以将它提取出来，放到单独的 `Component.stories.js` 文件中，从而省掉了重新定义 Storybook story case 的代码。

```javascript
#Button.test.js
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { enabled, disabled } from './Button.stories';
test('should increment when enabled', () => {
    const comp = render(enabled());
    userEvent.click(comp.getByText('Enabled: 0'));
    expect(comp.getByText('Enabled: 1')).toBeTruthy();
});
 test('should do nothing when disabled', () => {
    const comp = render(disabled());
    userEvent.click(comp.getByText('Disabled: 0'));
    expect(comp.getByText('Disabled: 0')).toBeTruthy();
 });
```

## Storyshot 快照测试

写完了组件样式，让我们通过视觉测试给 UI 组件加上一个安全保护伞。只需要给 UI Components 的 Stories 照个相就可以啦，结合 [Storyshots](https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-puppeteer#imagesnapshots) 自动生成 Snapshot Testing，成本几乎为 0。

```javascript
import initStoryshots from '@storybook/addon-storyshots'
import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'

const beforeScreenshot = (page, { context: { kind, story }, url }) => {
  page.setViewport({ width: 1440, height: 960 })
}

initStoryshots({
  suite: 'Image storyshots',
  test: imageSnapshot({
    storybookUrl: 'http://localhost:6006',
    beforeScreenshot,
  }),
})
```

结合 Storybook 引入 Storyshot，自动在 UI 完毕之后 take Snapshot，而且是图片级别的快照。

![](/files/-M1VmJxlqN9HEml_xvlc)

## TDD 与视觉测试保护伞

然后，在新的需求变更或是 CSS 重构之后，我们可以通过 Storyshot 自动打个快照，截图对比：

综上，个人认为 React Components 应当尽可能保持纯净，就安安心心做自己的 V(iew) 不好吗？lifecycle 什么的就不要 care 啦，现在使用 React Hook 便可非常直接得抽取逻辑，保持 functional component 纯净。而 pure UI 的组件，自然可以通过 Snapshot Testing 的方式轻松验证每次更改的合法与否。

```javascript
import React, { useState } from 'react'

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}
```

与其改动测试代码与实现代码，不如在 CDD 直接改动实现代码并验证过后，重新给 UI 组件照个相然后替换原有的相片即可，这样就能用新的相片（baseline）去验证未来可能**不小心**改动所影响的 break change，也就达到了 Testing 最初的目的。

![](/files/-M1jB7M71Eq7wdcdklOC)

> 左边 vs 右边，中间是 diff 的不同内容！

至此，「驱动」和「验证」两大目的均已达到，并能在一定程度上减少「成本」与提高「速度」。

而完全抽离出来的 Redux 逻辑才是真正与 data 相关的地方，absolutely 值得一测并且应当尽可能提高测试覆盖率。再者，在合理抽象与充分隔离的基础上，pure FP 测试起来也是一件非常 easy 和享受的事情，这个留到后边儿的练习项目再说。

![](/files/-M1VmJxnZ8xU1lJ7nqM6)

对于前端来说，TDD 业务逻辑和交互行为是最值得去做的，也是最容易去实施 TDD 的，你要学会有的放矢，把力气使在刀刃上，减少浪费。保持组件尽可能 pure component，然后组件样式什么的，就不要去傻傻得验证几个 px 啦，就直接通过 CDD 在 Storybook 里面看就好了。而图片快照和全局的 visual testing 会给我最直观的反馈和最细致的对比，截图比什么都来得快、来得直观。

![](/files/-M1jB7M9JFZ2O-nnwg7D)

在这儿还可以稍微脑洞一下，最极端的情况，追求像素级还原：视觉测试，可以直接拿设计稿来对比嘛！

![](/files/-M1jB7MB2CM_bJF3vUk5)

## 也请你思考一下

* 独立的 UI 组件运行时带来了什么好处？
* 前端 TDD 的不同阶段，侧重点分别是什么？
* 整理你的窗口布局，让反馈更加一触即达（IDE、命令行、浏览器、设计稿…）

> 然后……作为前端开发者，或许你需要第二块显示器屏幕。

![multiple display](/files/-M1jB7MD5Cqi7PFKidEj)
