现在已经可以很方便的使用使用ES6(亦或是未来的ES7)了,你只需配置好Babel就可以开始编码。如果你只是在NodeJS环境中开发,你甚至都不需要Babel,因为NodeJS自带的ES6支持已经越来越好了。
针对开发流程的工作流是非常简单和详细的,那么针对测试的呢?你该如何为ES6代码编写单元测试呢?又该如何配置测试工具以支持这些新特性呢?
在本文中我会介绍如何配置那些最流行的测试工具 —— Mocha,Jasmine,Karma以及Testem —— 以便让它们能与ES6一起工作。我们还会看一看测试ES6代码的最佳实践。今后你就再也不用忽视对ES6代码的测试啦!
在开始之前,我们需要先安装几个必要的工具:
即使你的项目已经完成了这些步骤的配置,你还是需要查看以下的章节。某些测试工具可能会需要一些有可能被你遗漏的库。
不管你使用的是何种测试,打包工具,你都需要Babel和babel-polyfill。正如你可能知道的那样,Babel自身用来把ES6的新语法转变为旧的JavaScript引擎可以理解的格式,而babel-polyfill则会提供旧引擎中缺失的ES6对象(例如Promise
)和函数(例如Array.prototype.find
)。
npm install --save babel babel-polyfill
如果你打算测试Node.js代码, 你还需要安装babel-register。该模块可以让你选择的测试工具在加载模块时自动对模块进行编译。
npm install --save babel-register
你可能还要安装你需要的Babel presets,比如es2015和react。
npm install --save babel-preset-es2015 babel-preset-react
虽然你可以通过命令行参数或是写入package.json
文件的方式传递Babel配置参数。但我还是推荐你使用一个.babelrc
文件。
Babel会自动从.babelrc
中加载配置。即使是你使用了一个调用了Babel的库,这也是适用的。把配置选项写入.babelrc
文件意味着你不必在多处维护这些信息了。
以下是一个使用了es2015和react两个preset的.babelrc
文件示例:
{
"presets": ["es2015", "react"]
}
NodeJS用户请注意: 如果你只是想在Node环境进行测试,那么你可以跳过此章节直接去到讨论选择测试工具的章节。
Browserify用户需要安装babelify transform库。该库允许Browserify在构建过程中使用Babel对代码进行转译。
npm install --save babelify
你可以通过命令行参数传递给browserify:
browserify -t [ babelify ] some-file.js -o some-output-file.js
也可以写入package.json:
"browserify": {
"transform": ["babelify"]
}
针对webpack,你需要安装babel-loader。webpack会利用该库对代码进行转译。
npm install --save babel-loader
然后在Webpack的配置文件中添加参数:
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel'
}
]
}
上边的配置会使用Babel转译node_modules
目录以外,扩展名为.js
或.jsx
的文件。排除模块目录可以显著的提高编译速度。
在配置好了必要的预备条件之后,我们现在可以开始着手配置测试工具了。
接下来的部分我们会详细介绍如何配置各个工具。再之后,我们会着重介绍如何编写测试。
在Node.js环境中你所要做的只是在执行Mocha时传入正确的参数:
mocha --compilers js:babel-register --require babel-polyfill
这会开启Babel对JavaScript文件的转译并会自动加载babel-polyfill模块。
整个命令对于手工输入来说有些太长了,你可以把它作为一个npm script加入到package.json中:
"scripts": {
"test": "mocha --compilers js:babel-register --require babel-polyfill"
}
需要注意的是Mocha默认会从test/
目录加载测试用例。如果你希望从其它目录加载,你需要指定加载目录:
mocha --compilers js:babel-register --require babel-polyfill --recursive path/to/tests
在上例中我们使用--recursive
参数用来保证即使测试用例存放在path/to/tests
的子目录中也会被正确加载。
在浏览器环境中,你需要使用Webpack或是Browserify编译所有测试文件。
browserify test/**/*.js -o tests-bundle.js
webpack test/**/*.js tests-bundle.js
上面的命令会把test/
目录中所有的JS文件打包到tests-bundle.js
中。
然后在你执行测试的HTML文件中引入tests-bundle.js
:
<!DOCTYPE html>
<html>
<head>
<title>Mocha Tests</title>
<link rel="stylesheet" href="node_modules/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
`<script src="node_modules/mocha/mocha.js">`</script>
`<script>mocha.setup('bdd')</script>`
`<script src="tests-bundle.js">`</script>
<script>
mocha.run();
</script>
</body>
</html>
加载文件的先后顺序并不会影响测试,只要保证在mocha.run()
语句执行前加载打包的测试文件即可。即使你在测试代码中使用require
加载了任何断言库或是其它的工具库,在执行测试的HTML你都不必引入它们。
对Node.js环境来说,Jasmine并不是一个理想的选择。虽然它能工作,但是配置起来比Mocha要更复杂一些。
不像Mocha,Jasmine并没有提供命令行参数用于配置转译。因此我们需用通过babel-node
来启动Jasmine。babel-node是针对node
的一层包装,它会通过Babel执行你的代码。
为了更简单的使用Jasmine,我们把它安装到本地的node_modules目录:
npm install -g babel-cli
npm install jasmine
为了让Jasmine正常工作,你需要先初始化它的配置文件:
node_modules/.bin/jasmine init
这会创建一个spec/support/jasmine.json
文件。然后你就可以通过修改该文件来配置诸如测试文件路径之类的信息。
接下来就可以利用Babel执行我们的Jasmine测试代码了:
babel-node node_modules/.bin/jasmine
同样的,我们可以把它作为npm script写入package.json文件:
"scripts": {
"test": "babel-node node_modules/.bin/jasmine"
}
在浏览器环境中,Jasmine的配置步骤和Mocha一样。使用你喜欢的打包工具将测试文件打包然后在测试执行文件中引入即可。
browserify test/**/*.js -o tests-bundle.js
webpack test/**/*.js tests-bundle.js
在使用Karma时,为了在浏览器中执行Babel转译过的测试,我们需要安装karma-babel预处理器模块。该模块会协助Karma使用Babel转译代码。
npm install karma-babel-preprocessor
一旦安装完成,你就可以在Karma配置文件中添加以下配置信息:
preprocessors: {
"src/**/*.js": ["babel"],
"test/**/*.js": ["babel"]
}
这将开启对src/
目录以及test/
目录下任何.js
文件的Babel转译。
Testem针对ES6的配置很简单。你只需要在testem.json中添加以下配置:
"serve_files": ["tests-bundle.js"],
"before_tests": "browserify -t [ babelify ] test/**/*.js -o tests-bundle.js"
serve_files
选项告诉testem需要发送给浏览器的额外的测试文件。由于我们使用了before_tests
选项来把测试代码打包进tests-bundle.js
,我们需要在serve_files
中指定它。
或者是使用webpack:
"serve_files": ["tests-bundle.js"],
"before_tests": "webpack test/**/*.js tests-bundle.js"
现在测试工具已经准备就绪了,让我们看看如何为ES6代码编写测试吧。以下的示例使用了Mocah和Chai,但原理同样适用于Jasmine。
基本情况和测试非ES6代码时一样。我们使用describe
和it
建立我们的测试用例,不同的是现在可以使用ES6的特性来优化我们的代码了。
创建一个名为test/
的目录并创建一个包含以下内容名为test/arithmeticTest.js
的文件:
const chai = require('chai').expect;
describe('Arithmetic', () => {
it('should calculate 1 + 1 correctly', () => {
const expectedResult = 2;
expect(1 + 1).to.equal(expectedResult);
});
});
上边的代码在测试中使用了ES6特性。在加载Chai时,我们使用了const
而不是var
。这意味着我们不会在不经意间重新定义该变量,并且它明确表明了我们不希望修改它的意图。
我们还使用了箭头函数。使用它,你可以在一定程度上简化代码,然而它也可能带来一些潜在的问题 —— 我会在后边的最佳实践章节讨论这一点。
最后,和加载Chai时一样,我们使用const
声明了期望的结果变量。这同样可以避免问题,同时也表明了它的值不应被改变这一意图。
我们可以使用前文提到的命令执行这个测试:
mocha --compilers js:babel-register --require babel-polyfill
通过传入回调函数done
就可以使用箭头函数编写异步测试:
describe('Timeout', () => {
it('should call the callback', (done) => {
setTimeout(() => done(), 500);
});
});
Mocha还自带Promise支持,这使得编写异步测试变得非常简单。我们会在最佳实践章节讨论这一点。
在测试中使用ES6的import也是可行的。切记:测试代码也是代码。既然我们已经配置好了测试工具,任何在你应用中使用的特性也都可以在测试代码中使用。
import { expect } from 'chai';
describe('Arithmetic', () => {
it('should calculate 1 + 1 correctly', () => {
const expectedResult = 2;
expect(1 + 1).to.equal(expectedResult);
});
});
这和使用require的效果完全相同。
接下来让我们看一看一些针对ES6的最佳实践以及你可能会遇到的陷阱。
在Mocha中请谨慎使用箭头函数。在某些情况下你需要使用this.timeout
来控制一个测试在超时之前的等待时间。如果你使用了箭头函数,那这个配置就不会生效。
出现这种情况的原因是箭头函数使用this
的机制。这导致Mocha不能正确的绑定它的辅助方法。如果你用不到这些辅助方法,那么你可以放心的使用箭头函数。
与Mocha类似,在Sinon.js中使用箭头函数也可能导致问题。
问题出在sinon.test
上。当你的测试中存在测试替身(test double)时使用它是个好主意,因为它会在测试结束时自动帮你释放被替身的对象。但是由于它使用了this
绑定,因此它无法在使用箭头函数时正常工作。
解决方案是要么在使用sinon.test
时避免使用箭头函数,要么通过beforeEach
和afterEach
来手工初始化和释放测试替身:
var sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
it('should do something with a sandbox', () => {
// 与sinon.test类似,这个stub会自动被清理
var stub = sandbox.stub();
});
使用Mocha测试使用了ES6 Promise的代码就是小菜一碟。Mocha内置了对Promise的支持,因此你可以在一个测试中返回一个Promise。Mocha会帮你处理:
import { expect } from 'chai';
it('should succeed when promise is resolved', () => {
const result = Promise.resolve('success');
return result.then(function(value) {
expect(value).to.equal('success');
});
});
通常使用了Promise之后你需要编写异步测试。但是由于Mocha自带Promise支持,我们可以在测试中直接返回一个Promise,而Mocha会等待直到它被resolve。
当测试返回的Promise被reject时,Mocha也会很聪明的标记它为失败:
it('this test always fails', () => {
return Promise.reject('error message');
});
要了解更多信息,请参考我的文章JavaScript单元测试中的Promise:权威指南。
Mocha支持Promise意味着当你需要测试带有Generator的代码时,你可以使用来自co模块的co.wrap
方法。
npm install co
然后使用它包裹你的测试代码就可以了:
it('should do something with generators', co.wrap(function*() {
var result = yield doSomeGeneratorThing();
}));
打包之后要调试你的代码可能会变得更加困难。由于打包后的文件包含了所有的代码,要想找到是哪个文件产生的问题变得很困难。
要解决这个问题,你可以在打包时开启source maps。
-d
参数。例如: browserify -d -t [ babelify ] test/**/*.js -o tests-bundle.js
devtool: 'source-map'
来开启source maps在命令行中执行测试代码时,source maps是不必要的。
测试ES6代码很简单,只需要给工具做一点点配置就可以了。在未来ES6得到更好的支持以后,你就可以摆脱这些配置了,除非你想通过Babel实现其它目的(比如支持ES7)。
使用ES6编写测试代码和不使用它时没什么两样。只要记住箭头函数可能导致的问题就行了。
那么你该使用那个工具呢?我推荐Mocha。由于内建了对Promise的支持,它对ES6测试的支持是最好的。同时它也可以很好的和现有库协同工作。
往期精选文章 |
---|
ES6中一些超级好用的内置方法 |
浅谈web自适应 |
使用Three.js制作酷炫无比的无穷隧道特效 |
一个治愈JavaScript疲劳的学习计划 |
全栈工程师技能大全 |
WEB前端性能优化常见方法 |
一小时内搭建一个全栈Web应用框架 |
干货:CSS 专业技巧 |
四步实现React页面过渡动画效果 |
让你分分钟理解 JavaScript 闭包 |
小手一抖,资料全有。长按二维码关注京程一灯,阅读更多技术文章和业界动态。