# 任务 1：Cypress E2E 测试

Hey，你好，我是 JimmyLv 吕靖，这是你开始 前端 TDD 练习的第 11 天，今天我们会以 CommentBox 项目作为练习，学习如何使用 Cypress 进行 E2E 测试。

不知道你有没有这样的感觉，前端 **UI 级别的测试**会有这样一些痛点：

1. class name 老变来变去的，依赖 css selector 的测试都得改
2. UI styling 根本没法测，多 1 个 px 或少 1 个 px 也太过分了吧…
3. 很多跨浏览器的 hack，不同实现还得写测试样式什么的兼容性？
4. 响应式是个什么鬼？为什么天底下有这么多种不同分辨率的屏幕！？
5. ……

准备好代码库，让我们来一起尝试面对这些问题吧！

```bash
git clone https://github.com/JimmyLv/tdd-commentbox.git
cd tdd-commentbox

yarn install #安装依赖
yarn start #启动应用
yarn cy:open #运行E2E测试
```

当然，我还通过 CodeSandbox 的方式作为你的快速入口：[https://codesandbox.io/s/github/JimmyLv/tdd-commentbox](https://codesandbox.io/s/github/JimmyLv/tdd-commentbox) (可能需要科学上网)，帮助你去除设置开发环境的障碍，一键打开 Cloud IDE 即可开始编码，右边则是你的网站运行效果预览，比如：[https://tdd-commentbox.jimmylv.now.sh/](https://tdd-commentbox.jimmylv.now.sh/)

{% embed url="https://codesandbox.io/embed/github/JimmyLv/tdd-commentbox/tree/master/?autoresize=1&fontsize=14&hidenavigation=1&theme=dark" %}
CodeSandbox 代码编辑
{% endembed %}

[![Edit tdd-commentbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/JimmyLv/tdd-commentbox/tree/master/?fontsize=14\&hidenavigation=1\&theme=dark)

## 练习目标

### 功能 1：基础评论框

一个最基本的评论框组件，带有作者头像、文本输入框，以及取消或评论操作按钮。

### 功能 2：留言列表

多个评论能够显示列表，并且包含列表数量和排序，并且每一条评论可以进行点赞或踩的操作。

## 本次任务的练习要求

使用 Cypress 测试驱动开发评论框组件的 功能 1 和 功能 2：

* 学习如何使用 Cypress 实施 TDD，可见即可得
* 使用 Cypress 测试不同分辨率下组件及页面的显示效果
* 注意，不要基于 CSS 属性作为选择器，阅读[Cypress 最佳实践](https://docs.cypress.io/zh-cn/guides/references/best-practices.html#%E5%85%83%E7%B4%A0%E9%80%89%E6%8B%A9)

## Cypress 使用案例

### E2E 用户行为测试

以添加 comments 为例，模拟真实用户行为。

```javascript
function addComment(commentText) {
  cy.queryByPlaceholderText('请输入新留言…').type(commentText)
  cy.queryByTestId('reply-comment').click()
}

describe('Comments Page', () => {
  beforeEach(() => {
    cy.visit('/')
  })
  it('should add new comment and show comment list', () => {
    const commentText = '唱跳Rap篮球！！！'

    cy.queryByTestId('comment-title').should('have.text', '留言板')
    cy.queryByText('留言为空').should('exist')

    addComment(commentText)

    cy.queryByTestId('comment-title').should('have.text', '1 留言')
    cy.queryByText('留言为空').should('not.exist')
    cy.queryByText(commentText).should('be.visible')
  })
})
```

### 响应式测试

添加 `cypress-image-snapshot` 测试，在页面回归时捕获不同分辨率下的视觉效果。

```javascript
# cypress/integration/visual-tests.spec.js
const sizes = [
  ['iphone-6', 'landscape'],
  'iphone-6',
  'ipad-2',
  ['ipad-2', 'landscape'],
  [1920, 1080],
]

const pages = ['/', '/blog/xxxx']

describe('Visual regression tests', () => {
  sizes.forEach(size => {
    pages.forEach(page => {
      it(`Should match previous screenshot '${page} Page' When '${size}' resolution`, () => {
        cy.setResolution(size)
        cy.visit(page)
        cy.matchImageSnapshot()
      })
    })
  })
})
```

### 兼容性测试

兼容性测试，也需要考虑。不同浏览器下的组件样式及行为，可能会有所不同。

![](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M1VmJSxhLWSbqdIq__i%2F-M1VmJxYcvRtSx51R1bQ%2Fcomponents-explorer.png?generation=1583245514242548\&alt=media)

Cypress 4.0 已经支持跨浏览的 runner，参考 [Introducing Firefox and Edge Support in Cypress 4.0](https://www.cypress.io/blog/2020/02/06/introducing-firefox-and-edge-support-in-cypress-4-0/)

![](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M1VmJSxhLWSbqdIq__i%2F-M1VmJx_CH_esUmnTsAr%2Fcross-browser-launch-cypress.png?generation=1583245514471353\&alt=media)

## 扩展阅读，思考一下？

在[为什么软件开发需要重构？ - 知乎](https://www.zhihu.com/question/19552812)这个问题下面，熊节提到：

> 今天的商业 IT 环境用一个词就可以概括：不确定。不确定带来颠簸和变动，不确定滋生怀疑和恐惧。

因为软件本身就无时无刻随着需求的快速变化而改变，那何来不改变的软件行为呢？「重构本质乃是不改变软件可观察行为与功能」这个前提是否依然成立呢？同理，运用在前端测试上来说，需求老变化，每次都得改测试可烦了，PM/UX 还没事儿跟你说这儿多个 px，那儿少个 icon 之类的…

可能有些同学就要得出结论：TDD 在后端好用在前端不好使。重构、测试驱动开发这些极限编程实践运用在后端业务需求，领域建模核心上来说很好使，这些东西也是从那时候开始发展而来的。但是现在这个商业时代各种客户端 UI 层出不穷，需求变动也比以往大得多，很多 APP 都是周更新，那这些方法论是不是也遇到了一些矛盾的地方？

![LEAN](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M1jB6hekAeEzuJfbO3s%2F-M1jB7M-98i9jt5viAFq%2Fbuild-measure-learn%20with%20idea-code-data.jpg?generation=1583487159564066\&alt=media)

精益创业的方法论强调“开发（build）- 测量（measure）- 认知（learn）”的反馈循环，既然外部的商业环境变化莫测，那我们就不断得去试，好的 idea 继续加强，反之则尽快丢弃，不要一条路走到黑。最好的办法就是以最低的成本试，达成同样一个目标，尽可能少做事。

因此，让我们换个思路，从精益的角度来谈一谈 TDD，而测试只是 TDD 的附属品。需求变化，变化过快，并不会让测试带来的价值变少。单元测试的输入，本身就是基于我们对设计和实现方案的假设。需求变了，接口变了，测试跟着变是当然的，只不过是假设的输入变了。

比如说开发的时候，写好的单元测试，输入也是经常变，但原来的测试是不是就没有价值了？当然不是，测试挂掉的所有用例会帮我找出所有调用点。我不需要自己再去手动找，这个变的接口影响了哪些地方，我也不想找。把测试输入改成变后的需求，运行测试，挂掉的全部修好，然后再真实起应用跑，基本全是好的，就是不是，debug 起来也很轻松。

如果说抱怨测试经常要改输入改输出，那没有了测试接口需求变起来，验得岂不是更惨？说需求变了，因而测试价值减少了，因而原来就最好不写减少浪费，那变化之后的这些需求你又用什么来验证呢？还不是回到没测试裸奔手工验的状态。

React 所带来的好处，数据驱动和函数式思想(纯 I/O)，特别是有明显输入输出和数据强相关的地方，也让测试变得简单、单元模块化。衡量成本这件事情还是很难量化，感性得说，也许你不喜欢写 UI 测试，如果只写数据相关的测试会很爽，那么如果 UI=f(data)这个等式在 React 中被严格实施的话，把 data 控制好测好，那么最终的 UI 也一定是好的，不测也罢？

其实只是一个测试策略的问题。让我在后续的练习中揭晓答案，如何使用 TDD 思想实现组件驱动开发（[Component-Driven Development](https://blog.hichroma.com/component-driven-development-ce1109d56c8e)）？

**也请你思考一下：**

* 测试金字塔是什么？
* 为什么测试策略是金字塔结构？
* 测试金字塔每一层的侧重点是什么？
