前言
我本身就职于某安防企业,正儿八经的传统企业,这两年还有个别称叫“大白马”,懂的人自然懂。做的产品说大一点和物联网沾边吧。服务的客户主要还是以大中型国企和政府机关,所以很多产品的使用群体没有互联网公司来的那么庞大,因此也造成了我们之前许多产品在设计和开发时重功能,抢交付,轻体验的坏毛病,不止是我们公司,我了解到的大多数同行业公司都有相同的毛病。但是互联网飞速发展的今天,这些以前留下来的毛病越来越被客户诟病,用户体验已经成为了产品核心竞争力的一部分。这些年来公司在这一点上也在积极的改变,伴随而来的就是测试越是越来严苛,现在我们有时候看见测试的妹子都是绕着走。这可能也是某种程度上造成我们现在团队单身比例比较高的原因吧<<^^>>。对于我们前端来说工作量就更加重了,每天上午被测试妹子各种打扰,下午参见各种需求分析和UI/UE评审,然后晚上开始写代码,好不容易代码写完了,本以为告一段罗了,可以有时间处理一下个人的问题了。哎!想多了,发测阶段才是噩梦的开始,有时候我们自己也想不通,为什么一到发测的时候各种问题就开始出现了,然后就是各种加班,大家感觉很累,但是又没什么成就感,就是重复的的修改,重复的自测,测得你怀疑人生。有时候真的好羡慕那些搞后端的,他们分分钟就知道程序有没有什么问题,而我们要一遍一遍的去操作页面,填写数据,提交数据,查看页面显示是否异常,哎…都是泪。可能说到这里有很多搞前端的兄弟已经生有同感了。于是,我也开始了走上了自动化测试的道路。
目录:
一 关于前端自动化测试最开始存在的疑问
其实关于自动化测试,我好早之前就了解过,由于自己写了一段时间的java,后来开始做前端开发,关于前端自动化测试我心中有一直有各种疑问,另一方面工作压力的激增,所以就没有真正的去了解和实践。
关于疑问主要只有下面七点:
一口气说了5点,完了吗?没有,我前面提到了自己就职于传统行业,那么“传统”二字怎么体现出来呢?接着说
综上所有因素,结果就是一个,罢了,务实一点,还是加班手动测吧。
二 什么让我又坚定不移的走上了自动化测试的道路?
前面讲了这么多前端实现自动化测试的难点,但是我还是依然觉决定去深入学习前端自动化测试,并积极的在团队中推广。究其原因,总结一下:
首先,我个人认为无论前端后端,只要是用代码写的,都需要进行自动化测试,测试的全不全我们暂且不说,测试需要伴随整个的开发过程,不能全部将发现问题的时间堆到测试部门介入后,这样一来产品发测的风险会很大,有可能会被打回来,严重影响产品发布。二来,就像我像前面提到的那样,可能发测前各种问题会蜂拥而至,造成自己天天加班,熬夜多了,你懂的。自动化测试可以帮我们提前暴露问题,节约我们手动跑测试用例的时间。
其次,目前市面上前端自动化测试的方案已经比较成熟了,我们前面提到的那些问题,大部分是可以得到较好解决的,社区里面前端达人们分享了许多关于前端自动化测试的经验。所以在前端自动化测试,在技术上是没有问题的,所以我们需要大胆的去尝试。
再者,这是前端发展的趋势,我们可以打开目前开源的热门框架,Vue,React,UI组件库iview,element-ui,以及常用的npm包的源代码看看,他们的目录结构里面绝对都含有自动化测试脚本,可见前端自动化测试不仅仅是花钱秀腿。历史的车轮是滚滚向前的,谁都停不下来,身为前端的我们也不能再是一个切图仔,页面小王子了。互联网技术的发展,用户对于产品的更高要求,产品的快速迭代,这些都要求我们前端开发人员需要具备更高的开发效率,而自动化测试貌似是我们加班之外最好的一个选择了。
最重要的一点,每次的发测太折磨了。
三 如何进行前端自动化测试?
首先,先了解一下前端自动化测试的分类
单元测试
对程序中某一块独立的业务模块进行测试,可以是一个小功能,也一个函数,这属于白盒测试。下面是单元测试常用的组合方案:
1. karma+Jasmine+PhantomJS
2. karma+mocha+PhantomJS
 首先两者没有什么本质的区别,mocha相对于Jasmine出来的要晚一点,可以使用的多种断言库,包括nodejs的assert断言。
我自己更习惯使用mocha,两种写法很相像的,切换起来也是比较容易的。
安装:
npm install karma-cli -g
利用工具自动生成测试配置项
karma init
然后通过键盘的方向建,现在测试框架的组合
PS E:\workpace\units> karma init
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> mocha
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> yes
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> PhantomJS
>
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>
Do you wanna generate a bootstrap file for RequireJS?
This will generate test-main.js/coffee that configures RequireJS and starts the tests.
> no
Which files do you want to include with <script> tag ?
This should be a script that bootstraps your test by configuring Require.js and kicking __karma__.start(), probably your test-main.js file.
Enter empty string to move to the next question.
>
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes
Config file generated at "E:\workpace\units\karma.conf.js".
npm install karma chai requirejs karma-mocha karma-requirejs --save-dev
经过上面的步骤就可以开始写测试案例了。
我在单元测试中测试的有:
//person.js
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
/*
* 个人介绍
*/
introduce(){
return `大家好,我是${this.name},今年${this.age}岁,我是一名前端开发工程师.`
}
/*
* 请求个人发表的文章列表
*/
getArticleList(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
const list = {
code:200,
data:[{
id:'001',
name:'前端自动化测试的实践之路',
}],
desc:'请求成功'
}
resolve(list)
},1000)
})
}
}
module.exports = Person;
下面开始写测试脚本,一般一个模块写一个测试脚本文件,比如要测前面的Person类,就在test文件中创建person.spec.js测试脚本文件
//person.spec.js
const person = require("../src/person.js");
const expect = require("chai").expect;
describe('person.js', () => {
it('1.测试个人介绍函数introduce', () => {
const name = "王洋洋",age = 26;
const Person = new person(name,age);
expect(Person.introduce()).to.be.equal(`大家好,我是${name},今年${age}岁,我是一名前端开发工程师.` )
})
})
这里我一般用来测试与后台数据交互的接口,现在的web基本上都是前后端分离的开发模式,前后端的数据基都是通过异步请求的方式获取到的,我们在自动化测试的时候有必要模拟所有参数然后对所有的CGI进行测试。这里接着在上面的person.spec.js中的person.js测试套件中写用例,顺便提一下describe定义的就是一个测试套件,it定义的是一个测试用例,一个套件中可以包含多个其他的测试套件或测试用例。下面模拟一个异步请求的测试用例 :
it('1.测试个人介绍函数introduce', (done) => {
const name = "王洋洋",age = 26;
const Person = new person(name,age);
Person.getArticleList().then(result=>{
expect(result).to.be.an('object');
done()
})
})
执行启动测试的命令
PS E:\workpace\units> mocha ./test/person.spec.js
person.js
√ 1.测试个人介绍函数introduce
√ 1.测试请求个人文章列表的异步请求 (1004ms)
2 passing (1s)
PS E:\workpace\units>
可以看到异步请求的测试结果中还顺带有请求响应的实践,这个对后面的性能测试,也是有用 的。当然,真是的业务功能逻辑不可能这样简单,这里只是提到了这两中情况怎么测,具体到真实的业务场景中需要根据具体的业务功能写更加复杂的测试案例。
上面两种测试用例是对js代码块的测试,目前前端提倡组件化,模块化,像什么Vue,React框架在各大公司盛行,我本身就是一个Vue框架的深度用户。这些框架衍生出来了各种UI组件库,比如Vue的iview,element-ui,React的uxcore…我们打开这些组件的npm包,我们发现他们的目录下面都有一个test文件夹,这里面有关于各个组件的各种测试案例。这里就是我们提到的对于封装的UI组件的单元测试,这个是很有必要的,因为在平时的工作中,我们也会写一些行业内的公用组件来进行重复利用的,这个组件也会在后续不断的迭代,如果没有自动化测试,每次迭代都需要手动把所有功能重新测试一下,想像一下,这需要多少时间,如果有自动化测试,5s中不到可能就OK了。好了,下面我们写一个Vue组件的测试案例,至于为什么是Vue,因为我React不熟…。万事万物都事相同的。其实vue官方脚手架中已经将自动化测试的选项加进入了,我们只需要在初始化项目目录的时候选择就行了
vue init webpack unit-demo
? Project name unit-demo
? Project description A Vue.js project
? Author wangyy <wang839305939@outlook.com>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests Yes
? Pick a test runner karma
? Setup e2e tests with Nightwatch? Yes
? Should we run `npm install` for you after the project has been created? (recommended) npm
vue-cli · Generated "unit-demo".
# Installing project dependencies ...
# ========================
这里与有可能安装依赖失败,如果失败可以重新用cnpm装一次
cnpm install
//安装结束后执行单元测试命令
npm run unit
unit-demo@1.0.0 unit E:\workpace\units\vuetest\unit-demo
> cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run
30 10 2018 18:26:26.142:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/
30 10 2018 18:26:26.159:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
30 10 2018 18:26:26.173:INFO [launcher]: Starting browser PhantomJS
30 10 2018 18:26:38.168:INFO [PhantomJS 2.1.1 (Windows 8.0.0)]: Connected on socket M5J-odoqXKpQY1WYAAAA with id 51250636
HelloWorld.vue
√ should render correct contents
PhantomJS 2.1.1 (Windows 8.0.0): Executed 1 of 1 SUCCESS (0.063 secs / 0.03 secs)
TOTAL: 1 SUCCESS
=============================== Coverage summary ===============================
Statements : 100% ( 2/2 )
Branches : 100% ( 0/0 )
Functions : 100% ( 0/0 )
Lines : 100% ( 2/2 )
================================================================================
PS E:\workpace\units\vuetest\unit-demo>
hello.vue组件测试用例
import Vue from 'vue'
import Hello from '@/components/Hello'
describe('Hello.vue', () => {
it('should render correct contents', () => {
const Constructor = Vue.extend(Hello)
const vm = new Constructor().$mount()
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App')
})
})
通过用例可以发现,Vue UI组件的测试关键点就是通过创建出dom元素,然后去检测dom元素中的特征值,然后判断该测试用例是否通过,这只是一个思路,具体要测试那些特性,还是要根据组件的复杂度来写案例。
e2e测试
也叫端到端测试,从测试目的上说它是用来测试一个应用从头到尾的流程是否和设计时候所想的一样。简而言之,它是从用户的角度出发,认为整个系统都是一个黑盒,只有UI会暴露给用户。从测试实现上来说就是以功能来作为最小测试单元,通过测试脚本模拟用户的行为然后驱动浏览器来自动测试这些功能,比如自登陆,退出,页面跳转等一系列用户和界面的交互的功能。这一点最开始我是处于懵逼状态,因为大多数情况下都是人为的去测试功能点,至于通过测试脚本去驱动浏览器。即使偶尔异想天开想过,也无从下手。当然,自己不知道,不代表别人不知道,当我们打开自动化测试的大门后,你会发现里面好多大牛在这方面已经有很深的耕耘了。我们需要做的就是沿着他们的研究轨迹,不断的去丰富我们在这方面的知识,如果有一天自己有什么想法也可以积极的探索,积极的和大家分享。不多说,还是从vue-cli创建的工程来了解e2e测试
在初始化工程好后,直接运行
npm run e2e
开始执行测试,然后我们会开打浏览器打开,了浏览器会显示"正在接受自动换测试软件的控制",测试结束后,浏览器关闭
PS E:\workpace\units\vuetest\unit-demo> npm run e2e
> unit-demo@1.0.0 e2e E:\workpace\units\vuetest\unit-demo
> node test/e2e/runner.js
Starting selenium server... started - PID: 19864
[Test] Test Suite
=====================
Running: default e2e tests
√ Element <#app> was visible after 62 milliseconds.
√ Testing if element <.hello> is present.
√ Testing if element <h1> contains text: "Welcome to Your Vue.js App".
√ Testing if element <img> has count: 1
OK. 4 assertions passed. (26.305s)
PS E:\workpace\units\vuetest\unit-demo>
e2e测试方案目前我接触到的有两种:
Nightwatch是一个基于Selenium WebDriver API的e2e自动化测试框架,可以使用js方法与css选择器来编写运行在Selenium服务器上的端到端测试,这种方式会拉起本地的浏览器,就像上面这副图一样,浏览器会显示"正在接受自动换测试软件的控制",当然至于具体打开什么浏览器就要看安装的webdriven是什么了。这种方式另外一个好处就是可以拉起不同浏览器,对不同浏览器进行兼容性测试。
Nightwatch有两种方式去调起浏览器跑测试
很明显vue-cli初始化工程的时候选择的是第一种方案,具体怎么操作可以去Nightwatch官网看看。
第二种方案PhantomJs+Casperjs是使用无头浏览器进行测试,PhantomJs我们可以把他看成一个chrome的无头浏览器,Casperjj对phantomjs和webpage模块的封装,让我们可以以链式风格的方式写代码。调用Casperjs相关API来操作应用和浏览器。这种方案特别的地方是可以不用在桌面打开浏览器就可以跑测试案例。具体怎么操作可以去PhantomJs官网看看。
具体选择哪种方案这个看个人喜好,有的就是要看见实物才放心,那就选择第一种;如果习惯悄悄地干活,那也可以选择第二种,我个人从体验上来说还是更加喜欢第一种,毕竟这种看着别人干活,自己只需要一个命令就搞定的事,还是比较哇咔咔的。
有了方案后,再来看看这些方案能不能满足e2e的测试需求,大致有那些需求呢?
视觉回归测试
简单来说就是测试应用整体界面是否达到了UI设计图的要求。
这个就比较困难了,也是前端自动化测试中最难的部分吧,毕竟这是感官层面上的事。当然也不是没有方案。这里介绍两种方案:
1. Gemini
Gemini(https://github.com/gemini-testing/gemini)项目是 Yandex 团队开发的视觉回归测试工具
2. PhantomCSS
PhantomCSS(https://github.com/Huddle/PhantomCSS)由 Huddle公司的James Cryer 带领开发团队编写。它依赖于 CasperJS 和 Resemble.JS。
其实上面两种方式基本原理都是通过截取应用图像然后和基准图像通过像素之间的差异值进行对比,然后得出测试结论,这是在实现的方式和以来的环境他又一定的差异。
视觉回归测试具体的实现,我们目前还没有针对这两种方案去实践,一方面是最近确实时间上有点紧张,另一方面个人人为,虽然这两种方式能进行自动化测试,但是在实用性上还是存在疑惑的,因为现在的web页面展现的内容相对比较复杂,页面内容也比较丰富。既然有方案就去尝试吧,后面有时间我去实践一下,然后再分享出来和大家共勉。
性能测试
前端的性能测试,打开Chrome DevTools就一目了然了:
通过调试窗口,能够清楚的看见页面各种资源的加载情况,看起来比较直观的,但是这还是需要人为的去打开调试窗口,刷新页面,然后通过我们的火眼金睛去发现其中的异常,这和我们所说的自动化测试好像有点不太相符,自动化测试的原则就是,能用代码解决的事,就不要扯其他的,写就完事了。
先看看前端性能测试技有那些技术指标:
然后根据这些指标来确定测试方案,目前我自己是使用的是 PhantomJs来进行性能测试的
PhantomJs前端介绍了他是一个Chrome内核的无头浏览器,所谓无头,是指我们在眼再桌面看不见,当然除了获取测试数据外还需要生成一份测试报告,先来看看如何使用phantomjs来获取相关页面加载性能的数据?
首先安装phantomjs:
npm install phantomjs
安装好后,我们就可以开始写测试脚本了
var page = require('webpage').create();
var loadStartTime =Date.now();
var pageUrl = 'https://www.taobao.com/';
var resources = {};
function analysisLoadTime(page){
page.evaluate(function() {
function calculateTime(time){
return (parseFloat(time)/1000).toFixed(3);
}
console.log("页面名称:",document.title);
console.log("详细耗时:")
var performance = window.performance.timing;
var connectEnd = performance.connectEnd
,connectStart = performance.connectStart
,domComplete=performance.domComplete
,domContentLoadedEventEnd=performance.domContentLoadedEventEnd
,domContentLoadedEventStart =performance.domContentLoadedEventStart
,domInteractive=performance.domInteractive
,domLoading = performance.domLoading
,domainLookupStart = performance.domainLookupStart
,fetchStart = performance.fetchStart
,loadEventEnd = performance.loadEventEnd
,domainLookupEnd = performance.domainLookupEnd
,loadEventStart = performance.loadEventStart
,navigationStart = performance.navigationStart
,redirectEnd = performance.redirectEnd
,redirectStart = performance.redirectStart
,requestStart = performance.requestStart
,responseEnd = performance.responseEnd
,responseStart = performance.responseStart
,unloadEventStart = performance.unloadEventStart
,unloadEventEnd = performance.unloadEventEnd
,secureConnectionStart = performance.secureConnectionStart
var PromotFotUnloadTime = navigationStart;
var redirectTime = redirectEnd-redirectStart;
var AppCacheTime = fetchStart-domInteractive;
var DNSTime =domainLookupEnd-domainLookupStart
var TCP_time = connectEnd-connectStart;
var request_time = responseEnd-requestStart;
var DomLoad_time =domComplete-domLoading;
var DomCOntentLoaded_time =domInteractive-domLoading;
var EventLoad_time = loadEventEnd-loadEventStart
var operate_time = loadEventEnd-navigationStart;
var white_time = domLoading-navigationStart
var first_view_time = domInteractive-navigationStart
console.log("页面白屏时间:",calculateTime(white_time));
console.log("首屏时间:",calculateTime(first_view_time));
console.log("资源请求耗时:",calculateTime(request_time));
console.log("用户可操作时间:",calculateTime(operate_time));
});
}
page.open(pageUrl, function(status) {
if(status=="success"){
var loadFinishTime =(Date.now()-loadStartTime)/1000;
console.log("页面加载成功,总共耗时["+loadFinishTime+"s]");
page.evaluate(function() {
console.log(document.title);
});
analysisLoadTime(page)
}else{
console.log("页面加载失败")
}
phantom.exit();
});
page.onConsoleMessage = function(msg){
console.log(msg);
}
page.onResourceRequested = function (req) {
resources[req.id] = {
url:req.url,
startTime:Date.now(),
endTime:null,
total:0
}
};
page.onResourceReceived = function (res) {
if(resources[res.id]){
resources[res.id].endTime = Date.now();
resources[res.id].total =(resources[res.id].endTime-resources[res.id].startTime)/1000+"s";
console.log("请求:",res.id,"-->耗时:[",resources[res.id].total,"]s")
}
};
这里面关系相关的API我就不去介绍了,度娘上面很多我就不赘述了。这里主要提一下关键点,就是浏览器自带的window.performance对象,里面包含了当前页面相关性能参数。
通过phantomjs中加载页面,去获取这个对象,然后对相关数据进行分析就可以得到页面相关的性能参数。当然,每次执行结果会不同,一方面和网络情况有关,另外一方面和缓存有很大关系,这个在写脚本的时候一定要注意到。
通过上面的的一顿学习,前面我提到的大部分问题都是可以找到比较合适的解决方案,但是还是有一点,就是再浏览器中如果加载有类似OCX,NP插件的话,就测不了。如果谁在这方面有比较好的方案,可以@一下我,相互交流一下,我也会在空余时间继续浏览这方面的测试方案。
总结
其实我对前端自动化测试最开始真像我前面提到的那些问题一样,一脸懵逼,但是通过不断的去学习和实践,现在对自动测试已经有了一个比较深刻的认识,面对不同的测试场景,也能想到相关的测试方案。希望这篇文章对前端自动化测试感兴趣而目前又有点懵逼的朋友有一点帮助。关键的一点还是要自己去实践,光看是弄不明白的。请忽略“通假字”。