说到自动化测试,许多开发团队都是据说过、尝试过,但最后都止步于尝试,不能将TDD(测试驱动开发)、BDD(行为驱动开发)的完整流程贯彻到项目中。思考其中的缘由:终究仍是成本抵不上收益。javascript
不少后端开发人员可能写过不少自动化的单元测试代码,可是对前端测试一头雾水。这是由于相对于后端开发人员的自动化单元测试,前端的自动化测试成本更高。css
自动化测试就是经过自动化脚本将一个又一个测试用例串起来,每一个测试用例都要模拟环境、模拟输入、而后断言输出。前端自动化最难的地方就是模拟环境、模拟输入和断言输出了!
咱们能够试想一下现实中的使用场景:html
模拟环境:首先前端代码是跑在不一样的终端环境上的,纯粹的使用某台机子的运行环境进行模拟是没法发现真正存在的问题。因此咱们的测试用例必须跑在真实的环境下,这里面包括不一样的机器:Android、ios、pc、macbook;不一样的系统:window十、window八、linux、mac;不一样的运行载体:IE、safari、chrome、firefox、Opera、Android webview、UIWebview、WKWebview;不一样网络环境:WiFi、4G、3G、offline前端
模拟输入:前端的输入很差模拟,在PC上有鼠标click,double Click、drag、mouseDown、mouseOver、input等等,在mobile上有swipe、tap、scroll、摇一摇、屏幕翻转等。相对于后端的单元测试,前端的输入种类繁多,每一种模拟起来都十分复杂,并且不少bug隐藏在几种连贯的输入以后才会复现。vue
断言输出:前端的断言不是简单的判断值是否相等,不少状况是即便值相等、效果彻底不同。
不少展现效果更是不能经过简单的断言来检测,好比区域是否能滑动,输入时键盘是否正确弹起等。java
当你跨越千山万水把上面的问题解决了,测试用例写好了,功能代码写好,完美!而后UE跑过来和你说那个这根线往左边移动一像素的时候,你会瞬间崩溃。可能这一像素你不少测试用例都得重写。因此前端自动化测试的成本真不必定抵得上收益。node
可是困难不表明解决不了,部分场景不适合不表明全部场景都不适合!linux
正由于面临这么多的困难,咱们的前端社区开发出了不少工具帮咱们解决这些问题。本章节主要是结合Vue这个框架介绍前端自动化测试的一些工具和方法。webpack
咱们使用vue-cli去新建一个vue的新项目,在这个项目中开启默认的unit tests和e2e testsios
bogon:work xiaorenhui$ vue init webpack vueExample ? Project name vue-example ? Project description A Vue.js project ? Author kukuv <kukuv> ? Vue build standalone ? Install vue-router? No ? Use ESLint to lint your code? No ? Setup unit tests with Karma + Mocha? Yes ? Setup e2e tests with Nightwatch? Yes
下面列举下这个新项目中涉及到的一些开源项目:
karma
Mocha
Nightwatch
phantomjs
sinon-chai
不少人看到这么多新名词必定头晕,心想一个单元测试咋须要懂这么多东西。状况是上面只是单元测试框架的一小部分、还有许多框架没有列出来。正由于前端的自动化测试面临着许多问题,因此咱们才有这么多的框架来帮忙解决问题。
咱们先来分析一下这个项目中的unit tests,这里面用到了 Karma、Mocha、sinon-chai、phantomjs。项目中已经有一个默认的单元测试例子。karma做为测试执行过程管理工具把Mocha、sinon-chai、phantomjs等框架组织起来。Mocha用来描述测试用例、sinon-chai用来断言、而后使用phamtomjs做为运行环境来跑测试用例。
npm install 将依赖的库都安装好,这里面phantomjs的依赖会比较难装,若是你之间没有安装过phantom,由于phantom比较大,并且加上国内的网络环境等缘由。若是phantomjs装不上能够尝试使用chrome做为运行环境,这须要安装 "karma-chrome-launcher",须要修改配置文件。
而后 npm run unit 跑一下unit tests,若是提示权限问题就 使用sudo 来提高下权限。跑完后咱们看一下目录结构
└── unit ├── coverage 代码覆盖率报告,src下面的index.html能够直接用浏览器打开 │ ├── lcov-report │ │ ├── base.css │ │ ├── index.html │ │ ├── prettify.css │ │ ├── prettify.js │ │ ├── sort-arrow-sprite.png │ │ ├── sorter.js │ │ └── src │ │ ├── App.vue.html │ │ ├── components │ │ │ ├── Hello.vue.html │ │ │ └── index.html │ │ └── index.html │ └── lcov.info ├── index.js 运行测试用例前先加载的文件,方便统计代码覆盖率 ├── karma.conf.js karma的配置文件 └── specs 全部的测试用例都放在这里 └── Hello.spec.js
// 加载全部的测试用例、 testsContext.keys().forEach(testsContext)这种写法是webpack中的加载目录下全部文件的写法 const testsContext = require.context('./specs', true, /\.spec$/) testsContext.keys().forEach(testsContext) // 加载全部代码文件,方便统计代码覆盖率 const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) srcContext.keys().forEach(srcContext)
config.set({ // 在几个环境里跑你的测试用例 // browsers: ['PhantomJS','Chrome'], browsers: ['Chrome'], // 默认加载几个框架 frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], // 使用那些汇报框架 reporters: ['spec', 'coverage'], // 预加载文件 files: ['./index.js'], // 预处理 preprocessors: { './index.js': ['webpack', 'sourcemap'] }, // webpack 配置 webpack: webpackConfig, webpackMiddleware: { noInfo: true }, // coverage 配置 coverageReporter: { dir: './coverage', reporters: [ { type: 'lcov', subdir: '.' }, { type: 'text-summary' } ] } })
上面使用的插件例如 mocha、spec、coverage除了karma默认自带的都须要你在npm
上安装对应的插件,例如如下
"karma": "^1.4.1", "karma-chrome-launcher": "^2.2.0", "karma-coverage": "^1.1.1", "karma-mocha": "^1.3.0", "karma-phantomjs-launcher": "^1.0.2", "karma-phantomjs-shim": "^1.4.0", "karma-sinon-chai": "^1.3.1", "karma-sourcemap-loader": "^0.3.7", "karma-spec-reporter": "0.0.31", "karma-webpack": "^2.0.2",
> vue-exampl@1.0.0 unit /Users/xiaorenhui/work/vueExample > cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run 07 09 2017 12:08:13.004:INFO [karma]: Karma v1.7.0 server started at http://0.0.0.0:9876/ 07 09 2017 12:08:13.007:INFO [launcher]: Launching browser Chrome with unlimited concurrency 07 09 2017 12:08:13.015:INFO [launcher]: Starting browser Chrome 07 09 2017 12:08:15.475:INFO [Chrome 60.0.3112 (Mac OS X 10.12.3)]: Connected on socket qDaxr51TuQCfQBcVAAAA with id 73077049 INFO LOG: 'Download the Vue Devtools extension for a better development experience: https://github.com/vuejs/vue-devtools' LOG LOG: 'data' Hello.vue ✓ should render correct contents Chrome 60.0.3112 (Mac OS X 10.12.3): Executed 1 of 1 SUCCESS (0.024 secs / 0.011 secs) TOTAL: 1 SUCCESS =============================== Coverage summary =============================== Statements : 60% ( 3/5 ) Branches : 50% ( 1/2 ) Functions : 0% ( 0/1 ) Lines : 60% ( 3/5 ) ================================================================================
我修改了一下Hello.vue这个组件,能够看到coverage 里精确的显示了测试代码的覆盖率,下面是我作的修改
export default { name: 'hello', data () { console.log('data'); function aa() { } if(false){ console.log('data aa'); } return { msg: 'Welcome to Your Vue.js App' } }, methods:{ aa(){ console.log('methods aa'); } } }
打开reporter下面的index.html咱们能够看到代码覆盖的具体状况。
点开Hello.vue更有直观的方式展现哪些代码被覆盖了,哪些没有。
既然咱们已经有了单元测试,那e2e测试有和单元测试有什么区别呢?Nightwatch是前端e2e测试的一个有表明性的框架。单元测试TDD的粒度很细,咱们会为许多函数、方法去写单元测试,而e2e更接近BDD。直白点说,就是TDD的测试单元是一个个函数、方法,而BDD测试的单元是一个个预期的行为表现。e2e作的事情就是打开浏览器,而且真正的访问咱们最终的页面,而后在这个真实的浏览器、真实的页面中咱们去作各类断言,而单元测试不会要去咱们去访问最终的页面,单元测试要保证的是一个个单元是没有问题的,但这些单元组合起来跑在页面上是否有问题,不是单元测试可以保证的,尤为是在前端这种模拟环境、模拟输入很是复杂的领域中,这是单元测试的短板,而e2e测试就是用来解决这些短板的。
咱们来看看项目中使用Nightwatch来进行e2e测试的例子
首先看一下目录
├── e2e │ ├── custom-assertions │ │ └── elementCount.js 自定义的断言方法 │ ├── nightwatch.conf.js nightwatch的配置文件 │ ├── reports │ │ ├── CHROME_60.0.3112.101_Mac\ OS\ X_test.xml │ │ └── CHROME_60.0.3112.113_Mac\ OS\ X_test.xml │ ├── runner.js bootstrap文件,起咱们的页面server和nightwatch文件 │ └── specs │ └── test.js 测试用例
selenium是一个用java写的e2e测试工具集,它的API被归入 w3c的webDriver Api中, nightWatch是对selenium的一个nodejs封装。全部咱们须要再配置文件中配置selenium。
src_folders: ['test/e2e/specs'], output_folder: 'test/e2e/reports', custom_assertions_path: ['test/e2e/custom-assertions'], // 对selenium的配置 selenium: { start_process: true, server_path: require('selenium-server').path, host: '127.0.0.1', port: 4444, cli_args: { 'webdriver.chrome.driver': require('chromedriver').path } }, // 测试环境的配置 test_settings: { default: { selenium_port: 4444, selenium_host: 'localhost', silent: true, globals: { devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) } }, chrome: { desiredCapabilities: { browserName: 'chrome', javascriptEnabled: true, acceptSslCerts: true } }, firefox: { desiredCapabilities: { browserName: 'firefox', javascriptEnabled: true, acceptSslCerts: true } } }
下面的runner须要先起一个咱们的网页服务而后再起nightWatch服务
var server = require('../../build/dev-server.js') server.ready.then(() => { // 2. run the nightwatch test suite against it // to run in additional browsers: // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" // 2. add it to the --env flag below // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` // For more information on Nightwatch's config file, see // http://nightwatchjs.org/guide#settings-file var opts = process.argv.slice(2) console.log(opts); if (opts.indexOf('--config') === -1) { opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) } if (opts.indexOf('--env') === -1) { opts = opts.concat(['--env', 'chrome,firefox']) } var spawn = require('cross-spawn') var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) runner.on('exit', function (code) { server.close() process.exit(code) }) runner.on('error', function (err) { server.close() throw err }) })
sudo npm run e2e后
> node test/e2e/runner.js > Starting dev server... Starting to optimize CSS... > Listening at http://localhost:8080 [] Starting selenium server... started - PID: 74459 [Test] Test Suite ===================== Running: default e2e tests ✔ Element <#app> was visible after 81 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. (3.951s)
在控制台上咱们能看到各类断言的结果