怎么在模块化前端项目使用Mocha?

@fwon 2014-08-11 09:48:40发表于 fwon/blog

前段时间为部门的项目引入了前端单元测试,主要是基于seajs引入mocha,现在把这篇文章修改了一下发布出来。

为什么引入单元测试?

在采用mvc框架做开发的过程中,我们不断积累出自己的一些核心组件和通用组件。
我们面临了越来越明显的代码维护问题,如果不提早针对这些组件开发单元测试,将来会面临更严重的代码管理和维护问题,
前人开发的组件没做单元测试,后人用了错误的组件却无法定位问题,不能形成一个完善的前端系统。所以我们有必要开始考虑这方法的问题。

为什么选择 mocha + chai ?

mocha的作者是大名鼎鼎的TJ Holowaychuk。该人同时是express,jade, stylus的作者。所以mocha框架也自然很受欢迎...
mocha的优点主要有如下几个:

  1. 异步方法的测试更容易
  2. 支持before,after(即beforeAll/afterAll)
  3. 与nodejs结合更自然

我们的项目总是会朝着前后端分离的方向走,所以不管是nodejs的引入,ApiServer的抽离,都会让mocha发挥它强大的作用。
至于chai,它是一个很好用的断言库,支持多种断言风格,能给你无缝链接的赶脚。该方案的详细使用方法可参考这里

前端单元测试实践

那么选定好框架后,怎样引入到项目中,并且我们要用它做哪些工作呢?

1. 目录结构
我们的所有前端代码在webapp中,测试代码放在tests,和statics位于同一级。

├── Gruntfile.js
├── package.json
└── webapp
    ├── statics
    └── tests

tests中的文件结构如下:

├── test.html
├── unit
│   ├── base
│   │   ├── api.base.js
│   │   ├── app.js
│   │   ├── cache.js
│   │   ├── global.js
│   │   ├── message.js
│   │   ├── model.base.js
│   │   ├── region.js
│   │   ├── view.base.js
│   │   └── view.js
│   ├── component
│   └── utils
│       ├── algorithm.js
│       ├── date.js
│       ├── message.js
│       ├── random.js
│       ├── sync.js
│       └── uievent.js
├── vendor
    ├── chai.js
    ├── mocha.css
    └── mocha.js

test.html 为入口文件,掌管所有测试用例,
vendor 提供一些框架支持,目前为mocha和chai。
unit 为单元测试的文件夹,
base 存放的是基类的测试用例,
utils 存放的是工具类的测试用例,
component 存放的是组件的测试用例。
其实这里的utils也可以理解为组件的一种,为了和我们的项目结构统一,区分出了utils。
2. 怎么加载测试用例
由于框架的基类基本是很少改动的,所以其测试用例也相对比较少改动。更多的是我们在编写公用组件或工具类的时候,要自己编写相对应的测试用例,
让其他同学能放心使用你的组件。在编写测试用例的过程中,你才能发现它的不足之处,才能考虑它可能出现的异常情况,所以不要认为自己编写的
测试用例肯定是能跑通自己的代码,错了,编写测试用例的过程就是让你的代码更健壮的过程!废话不多说了,我们来看一下代码怎么写。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Mocha FE tests</title>
<link rel="stylesheet" media="all" href="vendor/mocha.css">
</head>
<body>
<div id="mocha"><p><a href=".">Mocha FE Unit Test</a></p></div>
<div id="messages"></div>
<div id="fixtures"></div>
<div id='testElement'>
    <h1></h1>
</div>
<script src="vendor/mocha.js"></script>
<script src="vendor/chai.js"></script>
<script>
    mocha.setup('bdd');
</script>
</body>
</html>
<script src="../statics/js/seajs/sea.js"></script>
<script>
    var path = '../';
    seajs.use([
        path + '/tests/unit/base/view.base'
    ], function() {
        mocha.run();
    });
</script>

因为我们的模块化开发是基于seajs的,所以为了方便,这里也采用seajs分别对不同模块进行测试。
首先要引入mocha.js和chai.js,接着调用mocha.setup方法设置改测试为bdd模式。然后我们才可以开始加载测试用例。为了能在测试用例中方便引入被测试模块,我们故意引用了与其同目录下的seajs。
所以要在入口重新配置测试用例的路劲,添加path前缀。
加载完测试用例后,不要忘记回调 mocha.run() 这样才能让它跑起来。
3. 怎么编写测试用例

define(function(require){
    'use strict';
    var View = require('view.base'),
        $ = require('core/selector'),
        expect = chai.expect,
        assert = chai.assert;
    describe('View.base', function() {                          //View.base为模块名
        describe('constructor', function() {                    //contructor为方法名
            it('constructor without arguments', function() {    //对方法名进行覆盖测试
                var view = new View();                          //实例化
                expect(view.name).to.equal('base');             //输出断言
                expect(view.tagName).to.equal('div');           //输出断言
            });
        });
    });
});

mocha采用describe关键字进行用例描述,it编写用例代码。相当简洁和符合bdd风格。
看一下浏览器运行的结果:
roadmap.path

4. 无界面跑测试用例
显然每次运行测试用例都要打开浏览器是不现实的,那怎么在项目构建的时候自动跑测试用例呢?由于目前项目是基于grunt打包。
所以可以利用grunt的mocha插件。在Gruntfile.js中加入下面代码:

mocha: {
    test: {
        options: {
            timeout: 10000,
            run: false
        },
        src: ['webapp/tests/test.html']
    }
}
grunt.loadNpmTasks('grunt-mocha');
grunt.registerTask('unit', ['mocha']); 

在命令行执行 grunt unit
看看输出什么内容:
roadmap.path
显示102个通过,1个等待中,8个失败。并且下面会列出执行失败测试用例的位置和原因,绿色表示期望值,红色表示实际值。
5. 后话
到这里整个单元测试的基本流程就完成了,在开发测试用例的过程中,也发现了基类几个潜在的bug。
目前项目中主要完成了基类和工具类的测试用例,而组件类的需要在后续开发中补上。该工作将在组件整理后进行。
测试用例看起来都很简单,也就是一大堆断言。实际上它要求你本身对代码有比较深刻的理解,才能写出更细致更有价值的测试用例。
所以测试用例的编写往往也是很费时间的,对于代码开发者,编写测试用例则能够考虑到更多潜在的情况。
养成编写测试用例的好习惯,能让你的代码更健壮,能让更多人接受它并传播它。