基于jest和puppeteer的前端自动化测试实战
前端测试现状
经常听到后端同学说“单元测试”,前端写过测试用例的有多少?答案是:并不多,为什么呢?两个主要原因
1、前端属于GUI软件,浏览器众多,兼容问题让人头大,用户量有一定规模的浏览器包括:
IE8、IE9、IE10、IE11、chrome、FireFox、360浏览器、搜狗浏览器、QQ浏览器……
要在这么多浏览器上做几轮测试并不容易
2、前端界面变化快,很多时候界面比测试脚本迭代的更快,测试跟不上脚步,投入产出不成正比
以上两点导致前端测试不受重视,很多前端开发者可能工作数年仍未写过单元测试
英国的一个前端开发者做了一项前端测试工具调查发现,目前仍有43%的前端开发者没有做过任何前端测试,这是现状

该不该写前端测试,还是得视项目情况而定,一般标准的开源项目都会做单元测试,所以有必要了解一下前端测试大概是个什么东西
分类
前面一直说的是前端测试而不是单元测试,是因为前端不同于后端,前端是有界面的,测试应该分为单元测试和集成测试
所谓单元测试,就是测试一个函数或某个代码片段,通过模拟输入确保输出符合预期
实例1:以下是一个完整的测试用例,用来测试函数sum是否按预期的计算两个数字之和
const sum = (a,b) => {
return a+b;
}
describe('分组测试描述',() => {
test('test 1+1', () => {
expect(1 + 1).toBe(2);
});
})
解释一下两个关键字:
describe,作用是将test分组,影响 beforeEach/afterEach/beforeAll/afterAll四个方法的作用域,它有两个参数
第一个参数就是分组描述,描述这个分组是干嘛的
第二个参数是个回调函数,内部可以有多个test,test的作用是声明一个测试
test,作用就是声明一个测试,有三个参数
第一个同样是描述,描述测试内容
第二个也是回调,内部为详细测试内容
第三个是测试超时时间,默认为5s钟,做单元测试一般都是足够的,集成测试一般都是不够的,可以用jest.setTimeout(timeout)方法修改所有test默认超时时间
集成测试,测的是一个功能模块,比如用户注册功能,集成测试又包括UI测试,UI测试用于确保页面正常渲染
集成测试完全是用测试脚本去模拟用户操作,比如打开浏览器、点击注册链接、输入用户名密码、点击注册
UI测试怎么确保页面正常渲染?
两种方式:像素级对比和快照
像素及对比,就是首先人肉确认页面渲染正常,执行脚本对页面截个图,下次利用测试脚本截个图跟上次的截图的每个像素自动进行对比,如果每个像素都一样,那么测试通过
快照,这里的快照不是截图的意思,而是将页面渲染后的DOM结构生成一个序列化的文本,下次再次生成一个序列化的DOM文本与之对比,如果内容完全一样,测试通过,做快照测试,必须保证多次测试输出快照总是一致的,然而在react中,model经常变化,这时就要用mock模拟函数返回固定数据确保model不变,mock功能在下文有介绍
主流库
流行的单元测试包括jest、mocha、jasmine、……
流行的集成测试库包括puppeteer、casperJS、PhantomJS、……
jest的特点是零配置、即时反馈,它所有测试用例默认是并行执行的,速度快,它也可以配置成串行,在调试时比较有用,jest每个测试用例文件都是一个沙箱,在单个测试文件内部定义或修改全局变量,不会影响其它测试文件,jest由Facebook团队维护,对React友好,适合大型项目
mocha是一个精简而灵活的单元测试框架,它本身没有包含断言库和mock(模拟)功能,需要自行引入其它库,而jest和jasmine都自带断言库和mock功能,什么是mock,后面会介绍
puppeteer是个神器,它并不仅仅可以做自动化集成测试,它本身是个node库,自带chromuium浏览器(所以npm安装它比较慢),它提供了一些高级API通过DevTools协议控制headless chrome或chromuium,它也可以配置为使用有界面版的chrome,既然是浏览器,chrome能做到的它基本都能做到,chrome做不到的,它也能做到,用puppeteer做集成测试,测试用例是真正在真实的浏览器上执行的,下面几点都是它所擅长的
- 生成页面屏幕截图或pdf
- 自动提交表单,做UI测试、模拟键盘输入、鼠标操作等
- 创建一个最新的自动化测试环境,用最新的JavaScript和浏览器功能,直接在最新的chrome中做测试
- 捕获你网站的时间线跟踪,以帮助诊断性能问题
casperJS是一个基于PhantomJS的库,它封装了PhantomJS的API使它更容易使用,PhantomJS内置了webkit的内核,测试用例并不是跑在真正的浏览器上面
本文的重点是jest和puppeteer,下面是实例和API都是基于这两者
Setup
如果在执行jest测试用例之前需要做一些配置,在用例执行完做一些清除操作,那你需要了解下面4个API
断言
在编写测试时,您经常需要检查值是否符合某些条件。Expect就是干这个的,它有很多 匹配方法,实例1中的
expect(sum(1,1)).toBe(2);
意思就是断言函数sum执行的结果等于2,其它匹配方法包括但不限于:
- 判断某个变量是否定义:.toBeDefined();
- 比较某个值是否大于指定数字:.
toBeGreaterThan(number); - 检查对象length属性是否等于指定值:.
toHaveLength(number); - ……
mock定时器
业务代码中经常会用到定时器,包括setTimeout、setInterval,在做单元测试的时候,如果傻傻地等定时器一秒一秒走那就很浪费时间,大家都是一秒钟几十万上下的人,哪怕几秒钟也不会浪费,jest的mock功能,可以模拟定时器执行,有4个重要的API必须了解一下:
- jest.useFakeTimers() 声明在当前测试文件中使用模拟定时器,声明后,可以直接用expect(setTimeout).toHaveBeenCalledTimes(1)判断定时器调用的次数
- jest.runAllTimers() 立即执行所有定时器
- jest.runOnlyPendingTimers() 立即执行挂起的定时器
- jest.advanceTimersByTime(msToRun) 提前msToTun毫秒执行定时器
第1个API需要注意,仅仅声明jest.useFakeTimers(),定时器回调的代码并不会执行,第2、3、4个API都会真正执行定时器回调代码;
jest.runOnlyPendingTimers()执行挂起的定时器是什么意思?其实就是即将要执行的那一个定时器,下面这段代码,会调用两次setTimeout,第一次是jest.useFakeTimers()触发的,第二次是jest.runOnlyPendingTimers()触发的
function timeout() {
setTimeout(() => {
console.count('count');
timeout();
}, 10000);
}
jest.useFakeTimers();
test('useFakeTimers', () => {
timeout();
jest.runOnlyPendingTimers();
expect(setTimeout).toHaveBeenCalledTimes(2);
});
如果把上一段测试用例的jest.runOnlyPendingTimers()换成jest.runAllTimers()会进入死循环

mock函数
手动实现了一个forEach函数,要测试它是否按预期执行回调,这里模拟了一个回调函数mockCallback,模拟函数的好处是可以获取每次调用它的参数和它的执行次数,在项目中可以模拟请求返回指定数据而无需访问服务器
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
test('test forEach', () => {
const mockCallback = jest.fn();
forEach([0, 1], mockCallback);
// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);
// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);
// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);
});
异步
测试脚本中可能包含异步操作,如果不用异步方式写test,test执行到最后一行就认为测试完成,很可能测试失败
方式一:done回调,传入参数done,异步操作执行完后执行done()
// done
test('async test', done => {
function callback(data) {
expect(data).toBe('xx');
done();
}
fetchData(callback);
});
// return promise
test('async test', () => {
//判断当前测试有一个断言被执行
expect.assertions(1);
return fetchData().then(data => {
expect(data).toBe('xx');
});
});
方式三:.resolves/.rejects,同样必须return promise
test('works with resolves', () => {
expect.assertions(1);
return expect(user.getUserName(5)).resolves.toEqual('xx');
});
方式四:ES8的async/await,可以和.resolves/.rejects混合使用
// async/await can be used.
it('works with async/await', async () => {
expect.assertions(1);
const data = await user.getUserName(4);
expect(data).toEqual('xx');
}); // async/await can also be used with `.resolves`.
it('works with async/await and resolves', async () => {
expect.assertions(1);
await expect(user.getUserName(5)).resolves.toEqual('xx');
});
只执行该describe,其它describe会被忽略
和.only相反,只跳过该describe,在调试时很有用
puppeteer常用的几个API也了解一下
- puppeteer.launch() 实例化一个浏览器
- browser.newPage(url) 打开新页面
- page.goto(url) 跳转到url
- page.$(selector) 选择页面元素,返回的是元素句柄(ElementHandle),不是真实DOM节点,selector底层实现用的就是document.querySelector
- page.$$(selector) 同上,selector底层实现用的就是document.querySelectorAll,返回多个句柄
- page.$eval(selector, pageFunction[, ...args]) 同上,返回的是pageFunction的返回值,在pageFunction内可以获取到真实DOM节点,如获取元素ID,page.$eval('div', divs => divs.id);
- page.$$eval(selector, pageFunction[, ...args]) 同上,selector底层实现用的就是document.querySelectorAll
- page.click(selector[, options]) 点击指定元素
- page.type(selector, text[, options]) 改变元素的值,如果是react,会同时改变model层数据,就像真实用户输入
单元测试 VS 集成测试
两种测试方法各有优缺点,具体用哪种视项目具体情况而定
基于jest和puppeteer的前端自动化测试实战的更多相关文章
- 基于UiAutomator2+PageObject模式开展APP自动化测试实战
前言 在上一篇<APP自动化测试框架-UiAutomator2基础>中,重点介绍了uiautomator2的项目组成.运行原理.环境搭建及元素定位等基础入门知识,本篇将介绍如何基于uiau ...
- 《Selenium2自动化测试实战--基于Python语言》 --即将面市
发展历程: <selenium_webdriver(python)第一版> 将本博客中的这个系列整理为pdf文档,免费. <selenium_webdriver(python)第 ...
- selenium2自动化测试实战--基于Python语言
自动化测试基础 一. 软件测试分类 1.1 根据项目流程阶段划分软件测试 1.1.1 单元测试 单元测试(或模块测试)是对程序中的单个子程序或具有独立功能的代码段进行测试的过程. 1.1.2 集成测试 ...
- web前端自动化测试/爬虫利器puppeteer介绍
web前端自动化测试/爬虫利器puppeteer介绍 Intro Chrome59(linux.macos). Chrome60(windows)之后,Chrome自带headless(无界面)模式很 ...
- 关于《Selenium3自动化测试实战--基于python语言》
2016年1月,机缘巧合下我出版了<Selenium2自动化测试实战--基于python语言>这本书,当时写书的原因是,大部分讲Selenium的书并不讲编程语言和单元测试框,如果想在项目 ...
- 《Selenium 2自动化测试实战 基于Python语言》中发送最新邮件无内容问题的解决方法
虫师的<Selenium 2自动化测试实战 基于Python语言>是我自动化测试的启蒙书 也是我推荐的自动化测试入门必备书,但是书中有一处明显的错误,会误导很多读者,这处错误就是第8章自动 ...
- 「免费开源」基于Vue和Quasar的前端SPA项目crudapi后台管理系统实战之自定义组件(四)
基于Vue和Quasar的前端SPA项目实战之序列号(四) 回顾 通过上一篇文章 基于Vue和Quasar的前端SPA项目实战之布局菜单(三)的介绍,我们已经完成了布局菜单,本文主要介绍序列号功能的实 ...
- Selenium 2自动化测试实战
Selenium 2自动化测试实战 百度网盘 链接:https://pan.baidu.com/s/1aiP3d8Y1QlcHD3fAlEj4sg 提取码:jp8e 复制这段内容后打开百度网盘手机Ap ...
- Selenium 与自动化测试 —— 《Selenium 2 自动化测试实战》读书笔记
背景 最近在弄 appium,然后顺便发现了 Selenium 框架和这本书,恰好这本书也介绍了一些软件测试&自动化测试的理论知识,遂拿过来学习学习.所以本文几乎没有实践内容,大多都是概念和工 ...
随机推荐
- Confluence 6 自定义配色方案
Confluence 的管理员可以修改 Confluence 的色彩配色方案.站点的默认配色方案将会在站点的默认空间上同时生效. 希望修改站点的配色方案: 在屏幕的右上角单击 控制台按钮 ,然后选择 ...
- Confluence 6 有关 AD 的一些特殊说明
当应用程序对使用 Active Directory (AD) 的 LDAP 服务器进行同步的时候,同步的任务只对 LDAP 最近修改的数据进行同步而不是对整个数据库进行同步.因为是增量同步,在第一次完 ...
- mac 端口占用问题
查看端口号 终端输入:sudo lsof -i tcp:port 将port换成被占用的端口(如:8086.9998) 将会出现占用端口的进程信息. 杀死占用端口的PID进程 找到进程的PID,使用k ...
- lightoj1234 打表技巧:分块打表
/* 打不了那么大的表,所以只记录分块的信息即可 */ #include<bits/stdc++.h> using namespace std; #define maxn 1000005 ...
- selenium+python-文件下载(SendKeys)
前言 文件下载时候会弹出一个下载选项框,这个弹框是定位不到的,有些元素注定定位不到也没关系,就当没有鼠标,我们可以通过键盘的快捷键完成操作. SendKeys库是专业的处理键盘事件的,所以这里需要用S ...
- windows下bat批处理执行sql语句__Mysql
直接上代码: @ECHO OFF SET dbhost=主机名(例如:127.0.0.1)SET dbuser=用户名(例如:root)SET dbpasswd=用户密码(例如:root)SET db ...
- Java利用POI读取Excel
官网直接下载POI http://poi.apache.org/ package com.CommonUtil; import java.io.File; import java.io.FileIn ...
- 跨域 jQuery库ajax请求
XMLHttpRequest是原生ajax,缺点是使用起来比较繁琐. jQuery库提供了一组简洁的ajax请求方法. ajax() get() post() 具体使用参考官方API: http:// ...
- Just oj 2018 C语言程序设计竞赛(高级组)H: CBT?
H: CBT? 时间限制: 1 s 内存限制: 128 MB 提交 我的状态 题目描述 对于二叉树,如果这棵树的节点排布是按行从上到下,每行从左到右挨个放置,中间不会有空闲的节点. ...
- 使用android-ndk官方ndkbuild例子
Why this blog 现在(2018年9月27日),Android Studio中新建ndk项目都使用cmake而不是Android.mk+Application.mk的方式.但老项目需要维护, ...
