♻️
前端 TDD(测试驱动开发)
  • README
  • 总-课程目标
  • 总-课程安排
  • Coding
    • 第一题:FizzBuzz 数字游戏
      • 什么是 FizzBuzz?
      • 任务 0:练功前的热身
      • FizzBuzz 项目剖析
      • 任务 1:TDD 初体验
      • 任务 2:先利其器
      • 任务 3:再撸一遍
      • FizzBuzz 项目总结
      • 附录 1:Jest 测试基础及要点
      • 附录 2:Testing Library 组件测试基础
    • 第二题:MarsRover 火星车
      • 任务 1:Tasking 任务分解
      • 火星车实战
      • 火星车 Tasking 任务分解
      • 任务 2:MarsRover 实战演练
      • MarsRover Coding 演示
      • 任务 3:MarsRover 练习
      • MarsRover 项目总结
    • 第三题:CommentBox 留言板
      • CommentBox 项目剖析
      • 任务 1:Cypress E2E 测试
      • 任务 2:测试驱动组件单元接口
      • 任务 3:组件级别的快速反馈
      • 任务 4:由外到内的前端 TDD
      • CommentBox 项目总结
    • 第四题:Bookshelf 魔法书架
      • Bookshelf 项目剖析
      • 任务 1:练习 API 契约测试
      • 任务 2:组件化与数据流管理
      • 任务 3:Redux 数据流测试
      • 任务 4:简化 Redux 项目结构
      • Bookshelf 项目总结
      • 附录 1:什么才是真正的 RESTful 架构?
      • 附录 2:【译】Redux 和 命令模式
      • 附录 3:【译】什么是 Flux 架构?(兼谈 DDD 和 CQRS)
    • 第五题:ShoppingCart 购物车
      • ShoppingCart 项目剖析
      • React 哲学:Thinking in React
      • 任务 1:任务分解 - 驱动组件树拆分
      • 任务 2:综合应用 - 驱动组件接口设计
      • 任务 3:综合应用 - 驱动数据流管理
      • 任务 4:综合应用 - 驱动组件样式开发
      • ShoppingCart 项目总结
由 GitBook 提供支持
在本页
  • 练习目标
  • 功能 1:基础评论框
  • 功能 2:留言列表
  • 本次任务的练习要求
  • Cypress 使用案例
  • E2E 用户行为测试
  • 响应式测试
  • 兼容性测试
  • 扩展阅读,思考一下?
  1. Coding
  2. 第三题:CommentBox 留言板

任务 1:Cypress E2E 测试

上一页CommentBox 项目剖析下一页任务 2:测试驱动组件单元接口

最后更新于5年前

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 的方式作为你的快速入口: (可能需要科学上网),帮助你去除设置开发环境的障碍,一键打开 Cloud IDE 即可开始编码,右边则是你的网站运行效果预览,比如:

练习目标

功能 1:基础评论框

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

功能 2:留言列表

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

本次任务的练习要求

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

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

  • 使用 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()
      })
    })
  })
})

兼容性测试

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

扩展阅读,思考一下?

今天的商业 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 也一定是好的,不测也罢?

也请你思考一下:

  • 测试金字塔是什么?

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

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

注意,不要基于 CSS 属性作为选择器,阅读

Cypress 4.0 已经支持跨浏览的 runner,参考

在这个问题下面,熊节提到:

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

Cypress 最佳实践
Introducing Firefox and Edge Support in Cypress 4.0
为什么软件开发需要重构? - 知乎
Component-Driven Development
https://codesandbox.io/s/github/JimmyLv/tdd-commentbox
https://tdd-commentbox.jimmylv.now.sh/
Edit tdd-commentbox
CodeSandbox 代码编辑
LEAN