使用jasmine+sinon测试backbone+requirejs项目

我们必须为自己的代码写自动测试代码, 并且需要持续地对自己的代码进行回归测试, 因为:

  • 项目成员对模块间接口的理解必须一致, 测试代码是最好的文档.
  • 谁都没十足的把握保证自己的修改不影响别人的代码.
  • 没有快速而充分的测试作为保障, 就很难提倡快速重构.
  • 让代码成为资产, 而不是债务, 减少代码的维护成本.
  • 每个人都必须为自己的代码负责.

那么基于backbone+requirejs的前端项目应当如何实施测试?

当前已经有很多非常优秀的javascript测试框架, 包括jasmine, QUnit, mocha. backbonerequirejs的前端项目可以使用上述任何一种测试框架. 技术经理可以根据团队成员的技术背景, 喜好来决定选择哪一种测试框架.

下面就以Contacts应用为例, 简单demo如何在项目中实现基于jasmine+sinon的测试用例.

工程的目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|-coffee                # web应用coffeescript源代码
|-app                   # coffee编译之后的web应用js文件
|-dist
   |-debug              # concat所有js文件到一个js文件, 但未作minize
   |-release            # concat所有js文件到一个js文件, 并且minize
|-assets                # jquery/requirejs/underscore/backbone等库文件
|-tests
   |-coffee             # 测试程序的coffeescript源代码
      |-config.coffee   # requirejs配置文件
      |-runner.coffee   # 按照requirejs模块定义规范定义的jasmine测试执行模块
   |-js                 # coffee编译之后的测试程序js文件
   |-lib                # jasmine/sinon等测试用库文件
   |-SpecRunner.html    # 测试html文件, 用来执行broqser端测试代码
|-index.html            # app html文件
|-favicon.ico
|-grunt.js              # grunt任务配置文件

按照requirejs的模块定义方式定义测试模块

由于被测模块是由requirejs进行加载的, 因此, 我们也可以遵循requirejs的模块定义方式定义测试模块, 确保被测模块测试模块前加载完成:

model_spec.coffee
1
2
3
4
define [use!underscore', 'use!backbone', 'model/under/test'], (_, Backbone, model) ->
  describe "suite description...", ->
    it "spec description...", ->
      # test code here ...

下面是测试用例的代码(collection, model, view各实现了一个模块的测试, 仅供demo):

加载测试模块, 定义测试runner

通过定义模块依赖, 确保在执行runner方法前加载所有的测试模块.

SpecRunner (runner.coffee) download
1
2
3
4
5
6
7
8
9
10
11
12
# we should add all specs here to make sure all specs are loaded before execution.
define ["spec/models/contact"
        "spec/collections/contacts"
        "spec/views/contactitem"], ->
  runner = ->
    jasmineEnv = jasmine.getEnv()
    jasmineEnv.updateInterval = 1000
    trivialReporter = new jasmine.TrivialReporter()
    jasmineEnv.addReporter trivialReporter
    jasmineEnv.specFilter = (spec) ->
      trivialReporter.specFilter spec
    jasmineEnv.execute()

定义requirejs的配置文件, 执行测试runner

由于我们希望通过一个配置文件同时加载被测模块测试模块, 因此这里require.configbaseUrl项必须与web应用的该值保持一致.

require.config (config.coffee) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
require.config
  baseUrl: "../app"
  paths:
    libs: "../assets/js"
    jquery: '../assets/js/jquery/1.7.2/jquery'
    underscore: '../assets/js/underscore/1.3.2/underscore'
    backbone: '../assets/js/backbone/0.9.2/backbone'
    text: '../assets/js/require/plugins/text'
    templates: '../assets/templates'
    spec: "../tests/js/spec"

  shim:
    'backbone':
      deps: ['underscore', 'jquery'],
      exports: 'Backbone'

    'underscore':
      exports: '_'

require ["../tests/js/runner"], (runner) ->
  runner()

定义SpecRunner.html, 用来在浏览器端执行策测试代码

SpecRunner.html (SpecRunner.html) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Jasmine Spec Runner</title>

  <link rel="stylesheet" type="text/css" href="lib/jasmine.css">

  <script type="text/javascript" src="lib/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine-html.js"></script>
  <script type="text/javascript" src="lib/sinon.js"></script>
  <script data-main="js/config" type="text/javascript" src="../assets/js/require/require.js"></script>
</head>

<body>
</body>
</html>

下面是测试执行的结果:

遗留问题

  • 由于被测模块的加载是由requirejs完成的, 那么如何在加载时mock被测模块的依赖模块?
  • 如何和CI系统集成, 提供命令行方式的浏览器环境进行测试? 尝试过envjs, 但requirejs总在加载模块时出错, 还没能进一步研究出错细节.

Comments