前端测试初体验小结

第一次接触前端测试。以前写前端代码几乎没有测试,所谓的测试不过是打开浏览器刷新下所写的页面,点一点看有没有报错,有没有达到预想的效果,也就是所谓的黑盒测试。在遇到需要测试所写的函数(单元测试)的时候,也不会去考虑边界,只要能达到当前的需求即可。这次接触到前端测试,可以说是打开了一个新的视野,有了更多的选择性。本文将对自己目前所了解的前端测试做一个简单的总结。

单元测试

所谓的单元测试,就是对一个函数,类或者模块进行正确性验证。比如,对一个求绝对值的函数abs()进行测试,应该得到以下结果:

  • 分别输入2、2.3、0.23, 分别输出2、2.3、0.23

  • 分别输入-2、-2.3、-0.23, 分别输出-2、-2.3、-0.23

  • 输入0, 输出0

  • 输入的非数字,输出报错,抛出异常

测试完以上测试用例之后,即得到一个相对完整的单元测试。

在学习单元测试以前的做法就是,写完函数之后,在浏览器的控制台中多次使用abs方法并传递不同的参数,肉眼观察所得到的结果是不是自己期望结果。这种做法的缺陷是显而易见的,如果下次修改了该方法,那么需要再次手动测一下所有的测试用例,费时费力,并不科学。

NodeJs 的 assert

测试的一个直观的表达就是”给出各种可能的输入,看看输出是否达到预期”,即将输出和预期进行比较,看看是否满足”输出 达到 预期”,如果满足则测试通过,如果没有满足则测试不通过。

NodeJs 的 assert 对象提供了丰富的比较方法 来完成对输出和预期的比较,从而完成测试,每一个方法都描述了一种预期,取代了测试人员肉眼观察的过程。其中比较常用的有以下几个:

测试方法 被测对象的输出 测试人员的预期
.ok(value[, message]) value value为true
.equal(actual, expected[, message]) actual actual和expected相等 ==
.strictEqual(actual, expected[, message]) actual actual和expected全等 ===
.notEqual(actual, expected[, message]) actual actual和expected不等 !=
.notStrictEqual(actual, expected[, message]) actual actual和expected不全等 !==
.throws(block[, error][, message]) 执行block block抛出error,或抛出指定error

举个栗子:

对于上述的求绝对值的函数abs()使用assert断言,将下面脚本放在test.js中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var assert = require('assert');//引入assert
var abs = require('xxx/abs');//引入你的abs方法

assert.equal(abs(2),2);
assert.equal(abs(-2),2);
assert.equal(abs(2.3),2.3);
assert.equal(abs(-2.3),2.3);
assert.equal(abs(0.23),0.23);
assert.equal(abs(-0.23),-0.23);
assert.equal(abs(0),0);
assert.notEqual(abs(-2),3);
assert.notEqual(abs(2),3);
assert.throws(abs(null));
assert.throws(abs("33"));
assert.throws(abs({"a":2}));

在node环境中执行test.js,如果上述测试用例有一个未通过,则说明该函数就是有问题的,排查问题并修改了方法之后只需要再执行下测试脚本test.js即可,增加了测试效率;如果都测试通过,基本可以保证该方法没有问题。

node.js 的 assert用法详细见assert

should.js

should.js 也是一个断言库,和 NodeJs 下的 assert 断言库一个道理。

那么既然有了 assert 为啥还要用 should.js 呢?很明显的一个原因是,NodeJs 下的 assert 只能在 node 环境中使用,是 node 内置的模块,无法在浏览器环境中使用,所以就有了 should.js,这在后面使用 karma 加载浏览器环境中可以看到,当然 should 两边都可以用;另外,should.js 在功能上比 assert 更丰富,更完善。

简单的使用 should.js

should.js 提供了一系列类似 assert 的断言的方法,其中也包含 ok, equal, notEqual等前面提到的 assert 的方法,且功能一样。不过 should.js 的基本的调用方式是:

“待断言表达式”.should.断言方法(预期结果);

依旧以 abs() 函数为例,一个简单的断言写法如下:

1
abs(2).should.equal(2);//就这么简单

值得一提的是,should.js 支持链式调用,一个简单的例子如下:

1
2
3
4
5
6
7
var should = require('should');

var user = {
name: 'tj',
pets: ['tobi', 'loki', 'jane', 'bandit']
};
user.pets.should.be.instanceof(Array).and.have.lengthOf(4);

关于should的更多用法参见should.js api,以及should.js github

mocha

中文读音”摩卡”。

mocha是什么?mocha是测试框架,既能支持浏览器环境,也能支持 node 环境。

测试框架是干嘛的? 所谓测试框架就是帮我们运行测试用例的工具,并且可以打印一些我们想看到的信息,这样我们专注于写测试用例本身。举一个实际的例子,如果没有测试框架,某个测试用例执行失败,则整个测试都会被终止,后面的测试用例无法执行,而 mocha 则很好的帮助我们解决了这个问题。

一个mocha的简单用法如下,依旧以 abs 函数为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
describe('绝对值方法', function () {
it('2 的绝对值为 2', function () {
abs(2).should.equal(2);
});

it('null 的绝对值应该报错', function () {
abs(null).should.equal(2);//这里会报错,但是不影响后面的测试用例
});

it('-2 的绝对值为 2', function () {
abs(-2).should.equal(2);
});
});

如上述代码,每一个 describe 为一个测试集合,包含了一个组相关的测试,它是一个函数,可嵌套;每一个 it 为一个测试用例,表示一个单独的测试,为测试的最小单位,它也是一个函数。describe 和 it 的第一个参数都是描述信息,第二个参数都是实际执行的函数。其中一个测试文件中可以有多个 describe,一个 describe 中可以有多个 it。单个测试用例失败后不会影响到其他的测试用例。

mocha 中自身不具备断言库,因此需要引用别的断言库,比如 should.js,这个可以在 mocha 的配置文件 mocha.opts 中配置 “–require should”,前提是安装了 should.js

写完测试文件 test.js 后,可以使用 “mocha test.js” 命令来执行测试文件,完成测试,后面紧跟脚本的路径和文件名可以执行多个测试文件。如果只用 mocha 命令则需要把测试文件放在 /test 目录下,mocha 默认会自动查找 /test 目录最顶层的文件执行测试。

  • mocha 命令常用的参数有以下几个:

–recursive: 会执行 /test 目录下所有层级子目录的测试文件

–reporter [param]: 指定测试报告格式,默认为 spec,可选为可选格式

–watch: 检测脚本变化,一有变化就执行测试

–bail: 只要有一个测试用例没有通过,就停止测试

–grep [param]: 执行指定名称[param]的测试用例

–grep [param] –invert: 执行名称不为[param]的测试用例

可以在 mocha.opts 配置上述参数。

  • mocha 的其他优点:

支持before、after、beforeEach和afterEach等钩子函数,在钩子函数中可以编写初始化代码,在测试前或者测试后执行

支持异步测试,异步测试执行完后,需要在回调函数中执行 done 方法,且只能执行一次,示例如下:

1
2
3
4
5
6
7
8
9
10
describe('async', function () {
it('获取jQuery.js 并验证', function (done) {
$.ajax({
url:"https://cdn.bootcss.com/jquery/2.2.4/jquery.js",
success:function(){
done();//成功后需要执行 done() 方法
}
});
});
});

关于 mocha 的更多用法请查看官方文档

Karma

Karma 又是什么?晕了晕了。

A simple tool that allows you to execute JavaScript code in multiple real browsers.

Karma的github上给出的解释是上面那段话。翻译过来就是:”可以让你的 JavaScript 代码在众多真实浏览器中执行的简单工具”。

也就是说,Karma 是用来将 js 代码放在浏览器环境中执行的工具,运行 Karma 之后,会调用我们的浏览器环境来执行我们的 js 代码。所以,可以这么理解,Karma 并非全部为了测试而生,它只是刚好满足我们测试浏览器环境的需求而被拿来使用而已。事实上,它的确多用于测试,只是你还可以用它来做测试以外的事情。

npm install karma 之后我们就可以使用 karma 了。

karma 最基本的使用是通过配置文件来完成的,配置文件中不同的配置可以完成不同的功能。使用 karma init 可以帮助生成配置文件。

在 karma 中使用 mocha 需要安装 karma-mocha 包,配置文件中指定 frameworks: [‘mocha’]。

对要使用的浏览器,需要安装指定浏览器的 launcher 包。参考 launcher 的官方说明

完成配置后,karma start 启动 karma,karma 会自动启动一个 server,默认端口为 9876。如果,singleRun 参数配置 singleRun:true,则表示接入 CI,karma 自动执行一次后并退出;如果 singleRun:false,则 karma 的服务会驻守着,可以在浏览器端通过 http://0.0.0.0:9876/ 或者 http://localhost:9876/ 来访问,从而调用浏览器环境,装载 js,完成 js 代码的执行(别忘了,karma 就是帮助我们在调用浏览器端环境来执行 js 代码的),从而完成测试。

karma 的另一个强大之处在可以接入持续集成(CI)。

关于 karma 的更多使用请参考karma官方文档

Travis CI

Travis CI 是 CI(持续集成)的一个解决方案。所谓的持续集成就是将代码频繁的集成到主干,从而能快速的完成测试和部署,是软件开发和发布的一个重要环节。

我们怎么使用 Travis CI ?

进入到 Travis CI 的首页,使用 github 账号登录,Travis CI 会自动将你的 github 中公开的 repo 同步到 Travis 下;然后,在 Travis 中的 repo 列表中,把你需要接入的 repo 的”开关”打开即可(具体的操作一遍就知道了,Travis 的首页也有说明,一看就懂)。

和 Karma 一样,Travis 的使用也强依赖于配置文件 .travis.yml,该配置文件简单配置如下:

1
2
3
4
5
6
7
8
9
10
11
language: node_js
node_js:
- "9"
before_script:
- npm install -g mocha
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3 # give xvfb some time to start
sudo: required
addons:
chrome: stable

可以指定编译语言、版本、sudo 权限、浏览器等等各种信息,详细配置参考Travis CI 说明文档。在本地的 git 仓库中添加 .travis.yml 配置文件,配置完毕后,push 到 github 中。Travis 会自动检测到仓库变化,并检测到 .travis.yml 文件的存在,然后从 github 中把代码同步过去,在自己的环境中,根据配置文件的配置信息,下载代码和工具,并自动配置然后完成编译执行,最后输出执行结果。在其 web 端可以实时看到整个过程的 log 信息以及执行结果。

初次使用 Travis 进行前端测试时可能会遇到以下几个坑:

  1. .yml文件格式不对。.yml 属于对文件格式要求很严格的文件,因此一点格式错误都会导致 Travis 无法完成预期效果。
  2. chrome 的沙箱环境的权限问题,在 Travis 执行 karma 并调用 chrome,可以考虑在 karma 的配置文件中添加 –no-sandbox 配置
1
2
3
4
5
6
7
8
browsers: ['Chrome', 'ChromeWithOutSandbox'],

customLaunchers:{
ChromeWithOutSandbox:{
base: "Chrome",
flags: ['--no-sandbox']
}
}

并且在 Travis 中赋予权限,请参考

关于 Travis CI 还有很多高级用法等待我们去探究。

其他解决方案

  • 断言库

chai.js

  • 测试框架

jasmine

  • 持续集成

jenkins

参考文档

大概粗浅的梳理了各部分的关系和简单用法,高级用法还有待进一步学习。

Loading comments box needs to over the wall