测试的动机

测试用例的书写是一个风险驱动的行为, 每当收到 Bug 报告时, 先写一个单元测试来暴露这个 Bug, 在日后的代码提交中, 若该测试用例是通过的, 开发者就能更为自信地确保程序不会再次出现此 bug。

测试的动机是有效地提高开发者的自信心。

前端现代化测试模型

前端测试中有两种模型, 金字塔模型奖杯模型

金字塔模型摘自 Martin Fowler's blog, 模型示意图如下:

金字塔模型自下而上分为单元测试、集成测试、UI 测试, 之所以是金字塔结构是因为单元测试的成本最低, 与之相对, UI 测试的成本最高。所以单元测试写的数量最多, UI 测试写的数量最少。同时需注意的是越是上层的测试, 其通过率给开发者带来的信心是越大的。

奖杯模型摘自 Kent C. Dots 提出的 The Testing Trophy, 该模型是笔者比较认可的前端现代化测试模型, 模型示意图如下:

奖杯模型中自下而上分为静态测试、单元测试、集成测试、e2e 测试, 它们的职责大致如下:

  • 静态测试: 在编写代码逻辑阶段时进行报错提示。(代表库: eslint、flow、TypeScript)
  • 单元测试: 在奖杯模型中, 单元测试的职责是对一些边界情况或者特定的算法进行测试。(代表库: jestmocha)
  • 集成测试: 模拟用户的行为进行测试, 对网络请求、获取数据库的数据等依赖第三方环境的行为进行 mock。(代表库: jestreact-testing-library)
  • e2e 测试: 模拟用户在真实环境上操作行为(包括网络请求、获取数据库数据等)的测试。(代表库: cypress)

越是上层的测试给开发者带来的自信是越大的, 与此同时, 越是下层的测试测试的效率是越高的。奖杯模型综合考虑了这两点因素, 可以看到其在集成测试中的占比是最高的。

基于用户行为去测试

书写测试用例是为了提高开发者对程序的自信心的, 但是很多时候书写测试用例给开发者带来了觉得在做无用功的沮丧。导致沮丧的感觉出现往往是因为开发者对组件的具体实现细节进行了测试, 如果换个角度站在用户的行为上进行测试则能极大提高测试效率。

测试组件的具体细节会带来的两个问题:

  1. 测试用例对代码错误否定;
  2. 测试用例对代码错误肯定;

轮播图组件为例, 依次来看上述问题。轮播图组件伪代码如下:

class Carousel extends React.Component {
state = {
index: 0
} /* 跳转到指定的页数 */
jump = (to: number) => {
this.setState({
index: to
})
} render() {
const { index } = this.state
return <>
<Swipe currentPage={index} />
<button onClick={() => this.jump(index + 1)}>下一页</button>
<span>`当前位于第${index}页`</span>
</>
}
}

如下是基于 enzyme 的 api 写的测试用例:

import { mount } from 'enzyme'

describe('Carousel Test', () => {
it('test jump', () => {
const wrapper = mount(<Carousel>
<div>第一页</div>
<div>第二页</div>
<div>第三页</div>
</Carousel>) expect(wrapper.state('index')).toBe(0)
wrapper.instance().jump(2)
expect((wrapper.state('index')).toBe(2)
})
})

恭喜, 测试通过✅。某一天开发者觉得 index 的命名不妥, 对其重构将 index 更名为 currentPage, 此时代码如下:

class Carousel extends React.Component {
state = {
currentPage: 0
} /* 跳转到指定的页数 */
jump = (to: number) => {
this.setState({
currentPage: to
})
} render() {
const { currentPage } = this.state
return <>
<Swipe currentPage={currentPage} />
<button onClick={() => this.jump(currentPage + 1)}>下一页</button>
<span>`当前位于第${currentPage}页`</span>
</>
}
}

再次跑测试用例, 此时在 expect(wrapper.state('index')).toBe(0) 的地方抛出了错误❌, 这就是所谓的测试用例对代码进行了错误否定。因为这段代码对于使用方来说是不存在问题的, 但是测试用例却抛出错误, 此时开发者不得不做'无用功'来调整测试用例适配新代码。调整后的测试用例如下:

describe('Carousel Test', () => {
it('test jump', () => {
... - expect(wrapper.state('index')).toBe(0)
+ expect(wrapper.state('currentPage')).toBe(0)
wrapper.instance().jump(2)
- expect((wrapper.state('index')).toBe(2)
+ expect((wrapper.state('currentPage')).toBe(2)
})
})

然后在某一天粗心的小明同学对代码做了以下改动:

class Carousel extends React.Component {
state = {
currentPage: 0
} /* 跳转到指定的页数 */
jump = (to: number) => {
this.setState({
currentPage: to
})
} render() {
const { currentPage } = this.state
return <>
<Swipe currentPage={currentPage} />
- <button onClick={() => this.jump(currentPage + 1)}>下一页</button>
+ <button onClick={this.jump(currentPage + 1)}>下一页</button>
<span>`当前位于第${index}页`</span>
</>
}
}

小明同学跑了上述单测, 测试通过✅, 于是开心地提交了代码。结果上线后线上出现了问题! 这就是所谓测试用例对代码进行了错误肯定。因为测试用例测试了组件内部细节(此处为 jump 函数), 让小明误以为已经覆盖了全部场景。

测试用例错误否定以及错误肯定都给开发者带来了挫败感与困扰, 究其原因是测试了组件内部的具体细节所至。而一个稳定可靠的测试用例应该脱离组件内部的实现细节, 越接近用户行为的测试用例能给开发者带来越充足的自信。相较于 enzyme, react-testing-library 所提供的 api 更加贴近用户的使用行为, 使用其对上述测试用例进行重构:

import { render, fireEvent } from '@testing-library/react'

describe('Carousel Test', () => {
it('test jump', () => {
const { getByText } = render(<Carousel>
<div>第一页</div>
<div>第二页</div>
<div>第三页</div>
</Carousel>) expect(getByText(/当前位于第一页/)).toBeInTheDocument()
fireEvent.click(getByText(/下一页/))
expect(getByText(/当前位于第一页/)).not.toBeInTheDocument()
expect(getByText(/当前位于第二页/)).toBeInTheDocument()
})
})

关于 react-testing-Library 的用法总结将在下一章节 Jest 与 react-testing-Library 具体介绍。如果对 React 技术栈感兴趣, 欢迎关注个人博客

相关链接

React 现代化测试的更多相关文章

  1. React组件测试(模拟组件、函数和事件)

    一.模拟组件 1.用到的工具 (1)browerify (2)jasmine-react-helpers (3)rewireify(依赖注入) (4)命令:browserify - t reactif ...

  2. react.js 测试

    <html>    <head>        <title>hellow</title>        <script src="ht ...

  3. react 项目 测试

    Enzyme 来自 airbnb 公司,是一个用于 React 的 JavaScript 测试工具,方便你判断.操纵和历遍 React Components 输出.Enzyme 的 API 通过模仿 ...

  4. React Jest测试

    一. var jest = require('jest'); jest.dontMock('../CheckboxWithLabel.js'); describe('CheckboxWithLabel ...

  5. 前端自动化测试 —— TDD环境配置(React+TypeScript)

    欢迎讨论与指导:) 前言 TDD -- Test-Drive Development是测试驱动开发的意思,是敏捷开发中的一项核心实践和技术,也是一种测试方法论.TDD的原理是在开发功能代码之前,先编写 ...

  6. React 还是 Vue: 你应该选择哪一个Web前端框架?

    学还是要学的,用的多了,也就有更多的认识了,开发中遇到选择的时候也就简单起来了. 本文作者也做了总结: 如果你喜欢用(或希望能够用)模板搭建应用,请使用Vue    如果你喜欢简单和“能用就行”的东西 ...

  7. iOS程序员的React Native开发工具集

    本文整理了React Native iOS开发过程中有用的工具.服务.测试.库以及网站等. 工具 你可以选择不同的开发环境:DECO.EXPO或者你可以使用Nuclide+Atom,目前我使用EXPO ...

  8. react源码总览(翻译)

    用react也有段时间了, 是时候看看人家源码了. 看源码之前看到官方文档 有这么篇文章介绍其代码结构了, 为了看源码能顺利些, 遂决定将其翻译来看看, 小弟英语也是半瓢水, 好多单词得查词典, 不当 ...

  9. 搭建 Jest+ Enzyme 测试环境

    1.为什么要使用单元测试工具? 因为代码之间的相互调用关系,又希望测试过程单元相互独立,又能正常运行,这就需要我们对被测函数的依赖函数和环境进行mock,在测试数据输入.测试执行和测试结果检查方面存在 ...

随机推荐

  1. Masstransit的应用服务总线

    Masstransit 是一个非常优秀的基于消息进行通信的分布式应用程序框架,详情参考官网. 在介绍AA.ServiceBus之前,先介绍下几个概念. 分布式 分布式系统如何定义?这里引用一下Dist ...

  2. python以ATM+购物车剖析一个项目的由来及流程

    ATM+购物车 一个项目是如何从无到有的 ''' 项目的由来,几个阶段 0.采集项目需求 1.需求分析 2.程序的架构设计 3.分任务开发 4.测试 5.上线运行 ''' 需求分析: # 对项目需求进 ...

  3. net core 序列化与反序列化与遇到的几个坑

    之前在C#里面序列化直接引入命名空间后使用JavaScriptSerializer jss = new JavaScriptSerializer();就可以用, 而net core里面不这样用了,我们 ...

  4. IIS身份验证和文件操作权限(一、身份验证配置)

    最近有一个项目服务器需要升级,主要是Web项目.因为以前是只写代码,不管发布.所以在环境构筑方面就出现自己的知识盲点.盲点一:IIS的身份验证的作用盲点二:IIS的身份验证和文件操作权限的关系(重点) ...

  5. 程序员的长安十二时辰:Java实现从Google oauth2.0认证调用谷歌内部api

    最近公司在做一个app购买的功能,主要思路就是客户在app上购买套餐以后,Google自动推送消息到Java后端,然后Java后端通过订单的token获取订单信息,保存到数据库. Java后端要获取订 ...

  6. zookeeper集群搭建及常用场景实现

    本文完整源码地址 基于zookeeper的常用用法.分布式锁.分布式队列及leader选举实现 https://github.com/killianxu/zookeeper_example zooke ...

  7. Go组件学习——cron定时器

    1 前言 转到Go已经将近三个月,写业务代码又找到了属于Go的条件反射了. 后置声明和多参数返回这些Go风格代码写起来也不会那么蹩脚,甚至还有点小适应~ 反而,前几天在写Java的时候,发现Java怎 ...

  8. Web Worker 多线程

    Web Workers多线程 1  浏览器把所有事件都通过操作系统安排到事件队列中(例如:你去一个·窗口买菜,需要排队):浏览器使用单线程处理队列中的事件和执行用户代码(也就是单线程:web work ...

  9. windows上使用pip下载东西时报编码错误问题解决方法

    原因是pip安装python包会加载我的用户目录,我的用户目录恰好是中文的,ascii不能编码.解决办法是: python目录 Python27\Lib\site-packages 建一个文件site ...

  10. 使用JavaScript的XMLHttpRequest+fromdata 传递blob到后端

    需要上传网页录音文件到服务器,写的艰辛,终于好了,C#端的代码失败的留作纪念,JS端也有失败的案例,就不放上来了 JavaScript: var form = new FormData(); form ...