Chen Yangjian's Blog

Carpe diem - Seize the day

用 QUnit 做 JavaScript 单元测试

| Comments

前一阵跟同事讲 JavaScript 单元测试该如何搞起,找了些资料, 结合自己的应用情况,说了一些,但依然说得不好。因此写篇博文,记录一下。

按专业做测试的童鞋们的划分,单元测试属于白盒测试。做法是, 与待测模块使用相同的程序语言,引入该模块,引入测试框架,然后噼里啪啦写一堆测试用例。 Ruby 童鞋这一点做得很好,自带了一个 test/unit,Rails 童鞋更进一步,在 项目根目录的 test/unittest/functional 下分别会生成对应 model 与 controller 的测试类。同时还补上了一坨断言,详情请看 Rails Guides 之测试

JavaScript 短小精悍,自带的东西除了各种神奇语法,啥也没有;有一堆所谓的 Juicy Libraries 可供挑选,结果经常因为想用 B 框架的甲插件,自己却已经用了 A 框架而心生惆怅。其实兼收并蓄也没什么不好,大家网速都还可以的嘛。

扯远了哈,我的意思是,要做 JavaScript 单元测试,第一个问题就是先得挑一个框架。 StackOverflow.com 上有人弄了个 详细列表 ,有些已经陈旧了,例如 JSUnit,有些则偏整体测试解决方案一点,例如 John Resig 做的 TestSwarm.com。

我们先讲语言层面的测试框架本身好了,现在用得比较多的是 JSSpec、 Jasmine 和列表中没有提到的同是 John Resig 的作品 QUnit

先被我排除的是 JSSpec,因为在我接触到的前端项目里头,要么没有写单元测试, 要么没有用 JSSpec,所以我一直没有见过 JSSpec 的单元测试语法是什么样的; 不过把它列入考虑的原因也简单,没有专门的测试人员的 Facebook, 写 JavaScript 单元测试用的框架就是它 (来源)。

SeaJSKissy 用的都是 Jasmine, 应该就是玉伯的偏好了。Jasmine 的语法效仿自 Ruby 里的 RSpec。 这种语法风格的宗旨是更贴近自然语言,直接从源码生成程序说明文档。

example.js
1
2
3
4
5
describe("Jasmine", function() {
  it("makes testing JavaScript awesome!", function() {
    expect(yourCode).toBeLotsBetter();
  });
});

但我不太喜欢,觉得有点不伦不类的。另一个让我最终没选它的原因是, 测试输出的部分还得自己包装,好麻烦⋯⋯ 参考玉伯 SeaJS 代码中 runner 部分。

所以我选了 QUnit。好啦,我终于切题了,下面讲 QUnit 的用法。

QUnit 包含两个文件,qunit.js 与 qunit.css,它的断言结果是输出到页面 DOM 里的, 所以基于 QUnit 写的测试,都需要有个 html 页面,引入这俩文件,并且保证页面中有这样的 DOM 节点:

test.html
1
2
3
4
5
<h1 id="qunit-header">亲爱的单元测试</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests">
</ol>

你的测试代码应该长这样:

test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
test("a basic test example", function() {
  ok( true, "this test is fine" );
  var value = "hello";
  equal( value, "hello", "We expect value to be hello" );
});

module("Module A");

test("first test within module", function() {
  ok( true, "all pass" );
});

test("second test within module", function() {
  ok( true, "all pass" );
});

module("Module B");

test("some other test", function() {
  expect(2);
  equal( true, false, "failing test" );
  equal( true, true, "passing test" );
});

在测试页面中引入这个,就齐活了。

QUnit 的 示例 有很多,jQuery 本身, jQueryUI 的部分项目,都用这货做的单元测试。

基本的测试用例搞完了,接下去要考虑的事是如何 自动化, 做持续集成。我尚无实际应用经验,先按下不表。John Resig 抱怨过 JavaScript 测试的烦恼处, 多平台、多浏览器测试使得一个小改动都需要大量人工去测,因此弄了 Test Swarm,对前端童鞋来说,文章很有趣,推荐一看。

此外,淘宝UED 的云谦童鞋,去年就搞过一个云测试项目,感兴趣的童鞋可以去了解一下。

Comments