一直知道前端也是有测试的,但理解很肤浅,今天下定决心摸索一遍到底什么是前端自动化测试...
本例子通过vue-cli
生成的unit
与e2e
来探讨...
基础名词
一些前端测试的名词解释:
karma是一个基于
Node.js
的JavaScript
测试执行过程管理工具,其在测试中的作用相当于开发构建中使用的webpack
karma-webpack连接
karma
和webpack
的桥梁。不经过webpack
编译命令的文件是无法独立运行的,karma
需要了解你的webpack
配置,决定如何处理你的测试文件。karma-phantomjs-launcher是
phantomjs
在karma
中的启动器,由此引出了PhantomJS,一个基于webkit内核的无头浏览器,即没有UI界面,即它就是一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现。在查找相关资料时,也发现了其他的常规浏览器launcher
,比如:Chrome、Firefox、Safari、IE
等,以应对不同浏览器或多浏览器的测试需求。见Browserskarma-sourcemap-loader一个
Karma
插件,生成文件的sourcemap
karma-mocha让你在
karma
中使用Mocha一款功能丰富的javascript
单元测试框架,它既可以运行在nodejs
环境中,也可以运行在浏览器环境中karma-sinon-chai让你在
karma
中使用sinon-chai
断言库的插件, 提供丰富的断言方法,前置依赖有sinon-chai
、sinon
、chai
…karma-spec-reporter用于将测试结果显示到控制台。
karma-coverage用来生成代码覆盖率。
Nightwatch是一套基于Node.js的测试框架,使用Selenium WebDriver API以将Web应用测试自动化。它提供了简单的语法,支持使用JavaScript和CSS选择器,来编写运行在Selenium服务器上的端到端测试。
相关配置
unit
目录结构,主要测试单元是一个个函数、方法
└── 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
karma.conf.js
内容
module.exports = function (config) {
config.set({
// 要启动的测试浏览器
browsers: [ 'Chrome'],
// 测试框架
frameworks: ['mocha', 'sinon-chai'],
// 测试报告处理
reporters: ['spec', 'coverage'],
// 要测试的目标文件
files: ['./index.js'],
// 忽略的文件
exclude: [],
// 预处理文件
preprocessors: {
'./index.js': ['webpack', 'sourcemap']
},
// webpack
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true
},
// Coverage options
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
},
// true: 自动运行测试并退出
// false: 监控文件持续测试
singleRun: true,
// 以下是 vue-cli 没有生成的一些配置
// 文件匹配的起始路径
// basePath: '',
// 服务器端口
// port: 9876,
// 输出着色
// colors: true,
// 日志级别
// LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
// logLevel: config.LOG_INFO,
// 监控文件更改
// autoWatch: true,
// 超时处理,6s内没有捕获浏览器将终止进程
// captureTimeout: 6000
})
}
index.js
入口文件
// 加载所有的测试用例、 testsContext.keys().forEach(testsContext)这种写法是webpack中的加载目录下所有文件的写法
// 匹配的是specs目录,里面是存放的是测试用例
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)
// 加载所有代码文件,方便统计代码覆盖率
// 匹配的是src目录,除了main.js以外的所有文件。
const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)
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 测试用例
nightwatch.conf.js
内容
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.js
入口文件内容,先起一个我们的网页服务然后再起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
})
})
工具详解
chai
定义几个函数
const math = {
add: (...args) => args.reduce((num, value) => num + value),
mul: (...args) => args.reduce((num, value) => num * value),
cover: (a, b) => {
if (a > b) {
return a - b
} else if (a == b) {
return a + b
} else {
return -1
}
}
}
node
自带的断言测试
const assert = require('assert')
const {add, mul} = require('./math')
assert.equal(add(2, 3), 5)
引入chai
库测试,3个方法作用一样,断言风格不同而已
const chai = require('chai')
// should
chai.should()
add(2, 3).should.equal(5)
// expect
consr expect = chai.expect
expect(add(2, 3)).to.be(5)
// assert
consr assert = chai.assert
assert.equal(add(2, 3), 5)
expect
断言的优点是很接近自然语言,下面是一些例子
// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });
// 布尔值为true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;
// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);
// include
expect([1,2,3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;
// match
expect('foobar').to.match(/^foo/);
基本上,expect
断言的写法都是一样的。头部是expect
方法,尾部是断言方法,比如equal、a/an、ok、match
等。两者之间使用to或to.be连接
如果expect
断言不成立,就会抛出一个错误。只要不抛出错误,测试用例就算通过
Mocha
Mocha
的作用是运行测试脚本,首先必须学会写测试脚本。所谓"测试脚本",就是用来测试源码的脚本
Mocha
默认运行test
子目录里面的测试脚本 添加--recursive
参数可以运行test
目录下所有层数用例
基本用法:
describe('#math', () => {
describe('add', () => {
it('should return 5 when 2 + 3', () => {
assert(add(2, 3), 5)
})
})
describe('mul', () => {
it('should return 6 when 2 * 3', () => {
assert(mul(2, 3), 6)
})
// 只执行此条
it.only('should return 6 when 2 * 3', () => {
assert(mul(2, 3), 6)
})
// 忽略此条
it.skip('should return 6 when 2 * 3', () => {
assert(mul(2, 3), 6)
})
})
describe('mul', () => {
it('should return -1 when 2 < 3', () => {
assert(cover(2, 3), -1)
})
it('should return 1 when 3 > 2', () => {
assert(cover(3, 2), 1)
})
it('should return 4 when 2 = 2', () => {
assert(cover(2, 2), 4)
})
})
})
异步例子:
it
块执行的时候,传入一个done
参数,当测试结束的时候,必须显式调用这个函数,告诉Mocha
测试结束了
需要用-t
或--timeout
参数,改变默认的(2000)
超时设置。
// $ mocha -t 5000 timeout.test.js
it('测试应该5000毫秒后结束', done => {
var x = true;
var f = function() {
x = false;
expect(x).to.be.not.ok;
done(); // 通知Mocha测试结束
};
setTimeout(f, 4000);
});
测试用例的钩子:
Mocha
在describe
块之中,提供测试用例的四个钩子:before()、after()、beforeEach()和afterEach()
describe('hooks', function() {
before(function() {
// 在本区块的所有测试用例之前执行
});
after(function() {
// 在本区块的所有测试用例之后执行
});
beforeEach(function() {
// 在本区块的每个测试用例之前执行
});
afterEach(function() {
// 在本区块的每个测试用例之后执行
});
// test cases
});
benchmark
benchmark
是一个测试函数性能的库
var suite = new Benchmark.Suite;
// add tests
suite.add('RegExp#test', function() {
/o/.test('Hello World!');
})
.add('String#indexOf', function() {
'Hello World!'.indexOf('o') > -1;
})
.add('String#match', function() {
!!'Hello World!'.match(/o/);
})
// add listeners
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
// run async
.run({ 'async': true });
Puppeteer
Puppeteer是类似于Nightwatch
的一个Chrome
专用版,有更友好的api,是用来测试游览器环境的一个工具
也可用于爬虫,比如这个demo演示了爬取百度图片,相较于cheerio
,它的爬虫更模拟真实环境,不易反爬虫
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
Jest
哦,是不是这一大堆东西看晕了,用个karma
还要集成一大堆各种插件配置,这一点上真是跟webpack
一样了
就像有人受不了webpack
这一大堆配置所以有了前端构建集成工具Parcel
而Jest就是这样一个前端测试集成工具
Jest的官方文档支持中文,这里就不详细说明了,有兴趣可以去官网查看
相比于karma
最大特点就是快和方便,缺点就是没有karma
测试环境真实和自由
具体抉择,仁者见仁智者见智啦~~