Requirejs/Backbone/Jasmine前端项目和持续继承

Contact(源代码)示例项目使用了bbb作为项目构建工具, 作为gruntjs的扩展, bbb能很方便地完成:

  • Coffeescript的编译
  • 文件的清除和复制
  • 源代码lint
  • 编译LESS
  • 优化requirejs模块
  • js/css文件的合并和优化
  • 文件的压缩, 打包
  • 内置调试http服务器

并且项目中还使用bbb进行jasmine单元测试代码的编译. 所有的操作都可以通过定义bbb任务并以命令行方式进行运行, 唯一的一个例外是jasmine测试执行需要启动浏览器执行. 如果要在项目中实施持续集成, 就必须不能依赖浏览器而以命令行方式执行测试.

曾经尝试过envjs, 但在当前的实现中, envjs还不能顺利运行requirejs的异步模块加载(issue). Phantomjs是另外一种方案, 它可以很顺利地集成jasmine以及requirejs, 但是如果需要集成得很好, 必须得实现一个jasminereporter用来和Phantomjs进行通讯, 并且还得实现一个gruntjs任务插件, 用来调用Phantomjs执行测试, 以及生成测试报告. 这个工作量不大, 我也曾经完成了一个最简单的gruntjs任务来调用Phantomjs. 正在想着怎样进行重构的时候, 突然发现最新的bbb已经悄然导入了grunt-jasmine-task, 一个利用Phantomjs执行jasmine测试的gruntjs任务插件.

Ok, 那一切就简单了. 现在仅仅需要修改grunt.js配置文件来定义项目的jasmine任务. 当然, 之前必须安装Phantomjs(gruntjs网站上有关于如何安装的faq).

1
2
3
4
5
6
7
8
9
10
11
  grunt.initConfig({
    // jasmine task is to run specs using phantom, before running it, you must
    // make sure you have installed phantom following instruction on
    // https://github.com/cowboy/grunt/blob/master/docs/faq.md#why-does-grunt-complain-that-phantomjs-isnt-installed
    jasmine: {
      all: {
        src:['http://localhost:8000/tests/SpecRunner.html'],
        timeout: 300000 //in milliseconds
      }
    }
  });

启动http服务:

1
2
3
$ ./node_modules/bbb/bin/bbb server
Running "server" task
Listening on http://127.0.0.1:8000

然后在另一个终端运行jasmine任务:

1
2
3
4
5
6
7
$ ./node_modules/bbb/bin/bbb jasmine
Running "jasmine:all" (jasmine) task
Running specs for SpecRunner.html
.............
>> 31 assertions passed in 13 specs (1451ms)

Done, without errors.

在CI系统中, 一种可行的做法是使用shell脚本或者Makefile, 先启动http服务, 然后异步执行测试. 这是一个可行的方案, 但是grunt.js本身就是一个命令行工具, 为什么还要用shell或者make了?

grunt.js支持alias task, 我们可以这样定义一个任务别名:

1
grunt.registerTask('test', 'default server jasmine');

执行test任务等价于按照顺序执行default, server, jasmine任务. 我们希望这样能工作, 可惜的是, bbbserver任务是阻塞式的, 不会退出, 也就是说, 在它后面的jasmine任务永远不会被执行.

我们希望执行一个server任务, 它能异步启动一个非阻塞的http服务, 后面的任务可以使用这个服务, 并且当grunt进程退出的时候, 该http服务能自动退出. 下面代码是用nodeconnect实现的满足这个要求的staticserver任务:

staticserver.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
 * grunt
 * https://github.com/cowboy/grunt
 *
 * Copyright (c) 2012 "Cowboy" Ben Alman
 * Licensed under the MIT license.
 * http://benalman.com/about/license/
 */

module.exports = function(grunt) {

  // Nodejs libs.
  var path = require('path');

  // External libs.
  var connect = require('connect');

  // ==========================================================================
  // TASKS
  // ==========================================================================

  grunt.registerTask('staticserver', 'Start a static web server.', function() {
    // Get values from config, or use defaults.
    var port = grunt.config('server.port') || 8000;
    var base = path.resolve(grunt.config('server.base') || '.');

    var middleware = [
      // Serve static files.
      connect.static(base),
      // Make empty directories browsable. (overkill?)
      connect.directory(base)
    ];

    // If --debug was specified, enable logging.
    if (grunt.option('debug')) {
      connect.logger.format('grunt', ('[D] server :method :url :status ' +
        ':res[content-length] - :response-time ms').magenta);
      middleware.unshift(connect.logger('grunt'));
    }

    // Start server.
    grunt.log.writeln('Starting static web server on port ' + port + '.');
    connect.apply(null, middleware).listen(port);
  });

};

假如你看过gruntjs的代码, 你应当能看出来, 这其实就是gruntjs自带的server任务, 只是为了避免和bbbserver任务命名冲突, 这里将任务注册的名称从server换成了staticserver.

staticserver.js文件和其他gruntjs task文件一样存放在<工程目录>/tasks目录下, 确保这个任务能正确加载. 然后, 如下注册一个alias task命名为test:

1
2
3
  // Register test task, which will compile app and run the server and then do test.
  // Here we use 'staticserver' task (pure grunt static server) for testing.
  grunt.registerTask('test', 'default staticserver jasmine');

运行这个test任务就能到下面的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ ./node_modules/bbb/bin/bbb test
Running "clean:0" (clean) task
Removing: app
Removing: dist/debug
Removing: dist/release
Removing: tests/js

Running "clean:1" (clean) task
Removing: app
Removing: dist/debug
Removing: dist/release
Removing: tests/js

Running "clean:2" (clean) task
Removing: app
Removing: dist/debug
Removing: dist/release
Removing: tests/js

Running "clean:3" (clean) task
Removing: app
Removing: dist/debug
Removing: dist/release
Removing: tests/js

Running "coffee:app" (coffee) task

Running "lint:beforeconcat" (lint) task
Lint free.

Running "coffee:spec" (coffee) task

Running "staticserver" task
Starting static web server on port 8000.

Running "jasmine:all" (jasmine) task
Running specs for SpecRunner.html
.............
>> 31 assertions passed in 13 specs (2256ms)

Done, without errors.

参考

Comments