任务 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. ……

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

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 (可能需要科学上网),帮助你去除设置开发环境的障碍,一键打开 Cloud IDE 即可开始编码,右边则是你的网站运行效果预览,比如:https://tdd-commentbox.jimmylv.now.sh/

练习目标

功能 1:基础评论框

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

功能 2:留言列表

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

本次任务的练习要求

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

  • 学习如何使用 Cypress 实施 TDD,可见即可得

  • 使用 Cypress 测试不同分辨率下组件及页面的显示效果

  • 注意,不要基于 CSS 属性作为选择器,阅读Cypress 最佳实践

Cypress 使用案例

E2E 用户行为测试

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

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 测试,在页面回归时捕获不同分辨率下的视觉效果。

# 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()
      })
    })
  })
})

兼容性测试

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

Cypress 4.0 已经支持跨浏览的 runner,参考 Introducing Firefox and Edge Support in Cypress 4.0

扩展阅读,思考一下?

为什么软件开发需要重构? - 知乎这个问题下面,熊节提到:

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

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

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

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

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

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

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

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

其实只是一个测试策略的问题。让我在后续的练习中揭晓答案,如何使用 TDD 思想实现组件驱动开发(Component-Driven Development)?

也请你思考一下:

  • 测试金字塔是什么?

  • 为什么测试策略是金字塔结构?

  • 测试金字塔每一层的侧重点是什么?

最后更新于