前端自动化测试入门(should.js+Mocha+Karma+Travis-CI)

测试驱动开发能『迫使』我们在开发前设计好api再实现细节,开发过程中尽量做到单元解耦(否则单元不可测),也便于我们日后进行重构。在前端开发中我们一般在核心库和中间件的开发中使用单元测试,写ui界面时则一般只会『做个类似按键精灵的东西,自动点点点』。

在这里我会简单介绍一下怎么使用should.js+Mocha+Karma+Travis.CI做前端自动化测试。

他们是什么?

首先来解释一下提到过的那些工具都是些什么。

should.js

既然是要做测试,那么显然我们需要判断一下输出结果是否符合我们预期,如果符合就是测试通过。而断言就是帮助我们完成这个判断的。

在程序设计中,断言(assertion)是一种放在程序中的一阶逻辑(如一个结果为真或是假的逻辑判断式),目的是为了标示与验证程序开发者预期的结果-当程序运行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止运行,并给出错误消息。–维基百科

在Node.js中内置有一个assert模块用于断言。然而他提供的函数有限,有时候并不能满足需求。

在这里我们使用的是一个叫should.js的第三方断言库。他的API 非常语义,缺点是文档太烂。

Mocha

Mocha(“摩卡”)是一个Javascript测试框架,而所谓测试框架其实就是用来运行一些符合框架要求的测试脚本并给出测试结果以达到测试的目的,我们的测试用例就写在那些测试脚本中。除了Mocha以外,Jasmine(“茉莉花”)也是一个很流行的测试框架。

Karma

Karma是一个Javascript的测试运行器(Test Runner)。

在一个TDD(测试驱动开发)的过程中,我们经常需要做的,也是最关心的就是编写测试用例、编写实现、运行测试。然而在实际开发中,我们很多时候还要经常做一些类似手动刷新页面,手动引入相应文件等等繁琐的工作。而Karma则能帮组我们从这些繁琐工作中解放出来,我们需要关注的只是编写测试用例和编写实现等核心工作,karma会检测文件的变化,自动运行测试。

Travis CI

所谓CI(Continuous Integration,简称 CI)就是持续集成的意思。按我的理解,持续集成就是每当项目代码有变更(例如有项目成员push了一份代码上来),就会自动构建、测试并反馈测试结果。而不需要等到代码长得很大的时候再测试,那时候如果出问题也许就只能推倒重来了。

而Travis CI就是提供这样持续集成服务的一个工具。你用github登陆他的网站,选择一个需要服务的仓库(里面需要有相关的配置文件)。那么以后每一次有代码push到这个仓库的时候Travis CI就会自动构建、测试、反馈结果。

怎么用?

下面我会通过一个例子来讲一下简单的基于should.js+Mocha+Karma+Travis-CI的测试流程是怎么样的。当然这里并不能将这几个工具讲解得很透彻,更多用法和配置还请到官网查阅。

准备

首先当然是要安装node.js,然后编写package.json等文件。这里我们先不管Karma以及Travis-CI,只在依赖中写上should以及mocha。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// package.json
{
"name": "testExample",
"version": "0.0.1",
"description": "an example of testing",
"scripts": {
"test": "mocha"
},
"author": "weibinzhu",
"license": "MIT",
"devDependencies": {
"should": "^11.2.1",
"mocha": "^3.5.0"
}
}

我们约定目录结构如下:

1
2
3
4
5
6
7
8
└── testExample
├── Readme.md
├── package.json
├── .gitignore
└── lib
└── add.js
└── test
└── add.test.js

lib 用来存放我们的待测试的代码,test文件夹内存放测试代码,add.test.js是我们的测试脚本,一般与要测试的脚本同名,带上.test后缀。

接下来npm i一下,安装好依赖之后就可以开始了!

编写测试代码

首先我们设计一下api以及编写测试脚本了。

这里我们以实现一个大数相加函数add(a,b)为例子。我们让两个参数ab都是数字组成的字符串,返回一个由ab相加结果构成的字符串。例如add('1','1')==='2'

根据这样的api,于是我们写出这样的测试脚本:

1
2
3
4
5
6
7
8
9
10
11
12
// add.test.js
require('should');
// var add = require('../lib/add');
describe('大数相加测试',function(){
it('"12"加上"22"等于"34"',function(){
add("12","22").should.equal("34");
});
it('"843529812342341234"加上"236124361425345435"等于"1079654173767686669"',function(){
add("843529812342341234","236124361425345435").should.equal("1079654173767686669");
});
})

我来解释一下:

  • 由于我们这里使用了should.js这个第三方断言库,我们需要先引入should:require('should');
  • 留意脚本里面有一个describe和两个it。一个测试脚本里面应该有一个或更多的describe块,并且每个describe块中应该有一个或更多的it快。
  • describe叫做测试套件(test suite),表示这是一组测试,第一个参数是这组的名字,第二个参数是一个待执行的函数,在测试的时候会执行这个函数。
  • it叫做测试用例(test case,表示一个测试,第一个参数是这个测试的名字,第二个参数是一个待执行的函数,当进行到这个测试用例的时候就会执行这个函数。如果这个函数内部抛出了错误,那么代表这个测试用例测试失败。
  • 类似add("12","22").should.equal("34");这样的代码就是一个断言,如果add("12","22")的返回值不等于"34",那么久会抛出一个错误,这个测试用例自然就不会通过。可见should.js的api十分语义,一看就知道是什么意思。更多api请到官网查阅,这里就不赘述了。

现在我们可以回到命令行,输入$ npm test或者$ mocha看一下结果。mocha会自动运行test目录里面的测试脚本,但不会运行里面子目录中的测试脚本。或者你也可以指定运行哪一个测试脚本:$ mocha add.test.js

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> mocha
大数相加测试
1) "12"加上"22"等于"34"
2) "843529812342341234"加上"236124361425345435"等于"1079654173767686669"
0 passing (14ms)
2 failing
1) 大数相加测试 "12"加上"22"等于"34":
ReferenceError: add is not defined
at Context.<anonymous> (test\add.test.js:6:3)
2) 大数相加测试 "843529812342341234"加上"236124361425345435"等于"1079654173767686669":
ReferenceError: add is not defined
at Context.<anonymous> (test\add.test.js:9:3)
npm ERR! Test failed. See above for more details.

显然是不通过的,我们add都没写呢

编写待测试的脚本

现在让我们把这个add函数实现了,我们把代码写在lib/add.js里面,然后再add.test.js中引入:var add = require('../lib/add');

add函数的代码如下,具体为什么这么写可以看看我这篇博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 大数相加
* @param {[String]} a 操作数
* @param {[String]} b 操作数
*/
function add(a,b){
var res = "",
c = 0;
a = a.split("");
b = b.split("");
while(a.length>0||b.length>0||c){
c = c + ~~a.pop() + ~~b.pop();
res = c%10 + res;
c = c > 9;
}
return res.replace(/^0+/,'');
}
module.exports = add;

现在再运行一遍测试应该就通过了,通过的样子长这样:

1
2
3
4
5
6
7
> mocha
大数相加测试
"12"加上"22"等于"34"
"843529812342341234"加上"236124361425345435"等于"1079654173767686669"
2 passing (14ms)

加上Karma

现在让我们在dependencies里面加上"karma": "^1.7.0",,然后再执行一次npm install。待安装完之后,我们可以在命令行输入$ karma init,karma会问我们一系列的问题。我们按提示操作即可。具体到本例子,我是这样回答那些问题的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. Which testing framework do you want to use ?
(mocha)
2. Do you want to use Require.js ?
(no)
3. Do you want to capture any browsers automatically ?
(Chrome)
4. What is the location of your source and test files ?
(lib/add.js)
(node_modules/should/should.js)
(test/add.test.js)
5. Should any of the files included by the previous patterns be excluded ?
()
6. Do you want Karma to watch all the files and run the tests on change ?
(yes)

回答完之后karma就会自动在根目录创建一个karma.config.js文件。没错,其实我们可以不使用$ karma init,自己在根目录创建一个karma.config.js文件也是可以的。

注意到第四个问题了吗?此时我们已经让karma跟踪上所需的文件了,此时我们可以放心的去掉add.js里面的module.exports以及add.test.js里面的require('should');var add = require('../lib/add');

现在让我们启动一下Karma吧,在命令行输入$ karma start。一切顺利的话你会看到chrome自动打开,命令行里显示着这个:

1
2
3
4
5
6
02 02 2018 22:14:09.880:WARN [karma]: No captured browser, open http://localhost:9876/
02 02 2018 22:14:09.896:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/
02 02 2018 22:14:09.896:INFO [launcher]: Launching browser Chrome with unlimited concurrency
02 02 2018 22:14:09.934:INFO [launcher]: Starting browser Chrome
02 02 2018 22:14:11.912:INFO [Chrome 64.0.3282 (Windows 10.0.0)]: Connected on socket Lx8YZORCy1yfAyFAAAAA with id 8475447
Chrome 64.0.3282 (Windows 10.0.0): Executed 2 of 2 SUCCESS (0.016 secs / 0.003 secs)

一次通过!

此时如果我们修改一下被跟踪的代码,例如在add.test.js里面将add("12","22").should.equal("34");改成add("12","22").should.equal("4");,保存之后再回到命令行,我们会看到这个:

1
2
3
4
5
6
Chrome 64.0.3282 (Windows 10.0.0) 大数相加测试 "12"加上"22"等于"34" FAILED
AssertionError: expected '34' to be '4'
at Assertion.fail (node_modules/should/should.js:1638:17)
at Assertion.value (node_modules/should/should.js:1715:19)
at Context.<anonymous> (test/add.test.js:6:25)
Chrome 64.0.3282 (Windows 10.0.0): Executed 1 of 2 (1 FAILED) (0 secs / 0.002 seChrome 64.0.3282 (Windows 10.0.0): Executed 2 of 2 (1 FAILED) (0 secs / 0.002 seChrome 64.0.3282 (Windows 10.0.0): Executed 2 of 2 (1 FAILED) (0.014 secs / 0.002 secs)

测试失败了!显然Karma检测到文件有改动后自动又运行了一次,当我们把代码改回来之后karma又会执行一次。这就是karma的魅力所在。

接入Travis CI

Travis CI是配合github使用的,因此我们首先需要将刚才的代码放上github。然后进入Travis CI的官网(可能需要科学上网)并用你的github账户登录。接着根据官网上的提示操作,也就是这个:

image

先选择需要接入的仓库(也就是我们刚上传的那个)然后在根目录添加.travis.yml文件。完成之后每当我们像github push一次代码,Travis CI就会默默地帮我们测试并在他的网站上给出结果。

显然这里的重点就是.travis.yml这个配置文件。我们可以在这个文件里面设置很多东西,不过这里只会用最简单的情况来介绍一下。更多设置还请到官网慢慢查看。

1
2
3
4
5
6
7
8
language: node_js
node_js:
- "lts/*"
script: node_modules/karma/bin/karma start karma.conf.js --single-run
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

解释一下:

  • language自然是用来告诉travis我们是node.js
  • node_js是指明我们的node版本,这里是所有lts版本
  • script就是知名需要执行的脚本,这里就是执行karma。一般karma是会一直执行的,显然在travis中我们不需要这样,因此加上–single-run 修饰
  • before_install,这里涉及了一个浏览器的坑,下面详细说说。

我们知道karma会自动打开一个chrome(或者其他指定浏览器),因此我们需要告诉travis我们需要打开浏览器,并且由于travis默认不是用chrome的,我们还需要针对chrome分别在 .travis.ymlkarma.config.js做一些修改。

.travis.yml的修改已经在上面展示过了,下面说一下karma.config.js的改动。

我们需要在配置里面加上这个:

1
2
3
4
5
6
7
// karma.config.js
customLaunchers: {
Chrome_travis_ci: {
base: 'Chrome',
flags: ['--no-sandbox']
}
},

其次为了让我们的配置文件既适用于travis也适用于本地,我们需要对环境变量做个判断:

1
2
3
4
5
6
7
8
9
10
11
// karma.config.js
module.exports = function(config) {
var configuration = {
// 原来的各种配置
}
if(process.env.TRAVIS){
// 做一个判断
configuration.browsers = ['Chrome_travis_ci'];
}
config.set(configuration)
}

修改完成之后将代码push到github,此时回到travis CI的网站,我们应该会看到travis正在测试我们的代码(可能需要等几分钟travis才会响应到我们刚才的push)。

测试完成之后,如果最下面出现Done. Your build exited with 0.并且仓库名字隔壁的图标变成这个:image,证明测试通过了。

最后说几句

这只是最最基本的一个入门,显然这里面还有很多东西需要深究。除了官网外我还有几个链接分享给大家:

这是travis官网上有关浏览器的文章

阮一峰大大关于mocha的教程

另一篇关于mocha教程

本文用到的那个例子我放在了这个仓库中