JavaScript 单元测试框架:Jasmine 初探
来自: http://www.ibm.com/developerworks/cn/web/1404_changwz_jasmine/
简介
随着互联网浪潮的逐渐兴起,各种基于互联网的云战略也不断涌现,各个公司对云平台的理解和实现不尽相同,而云+端的模式越来越多受到关注。其中的端可以理解为终端用户手中的各种终端,包括 PC、手机、平板等不一而足。 而越来越多的用户愿意在自己的设备上使用轻量级的基于浏览器的应用。这类应用的安装部署可以是通过插件的方式安装,也有可能是直接以网页的形式访问而无需安装,相对于富客户端的下载安装,对用户来说更加简单方便,用户体验也更好。
这类应用对开发人员来说,需要一些互联网相关的技术,其中必不可少 HTML CSS 和 JavaScript 技术。而 JavaScript 作为一种客户端脚本语言,和传统编程语言 Cpp、Java 等相比,没有诸如 Eclipse、Visual Studio 等集成开发调试环境,其调试和测试是对开发人员都是一项挑战。
目前 JS 单元测试框架有丰富的选择,比如 Buster.js、TestSwarm、JsTestDriver 等。而 Jasmine 作为流行的 JavaScript 测试工具,很轻巧只有 20K 左右,而功能丰富,让我们可以容易的写出清晰简洁的针对项目的测试用例。对基于 JavaScript 开发的项目来说,是一款不错的测试框架选择。
搭建环境
获取安装包
可以在开源社区网站下载最新的 Jasmine 安装包, 目前的 Standalone 的最新版本是 1.3.0. 下载地址: https://github.com/pivotal/jasmine/downloads
配置安装
下载后的.zip 文件包解压缩,如下的目录结构:
图 0.目录结构
其中 lib 文件夹中包含 Jasmine 的源代码。采用如下相对路径可以包含 Jasmine,进而开发基于 Jasmine 的测试用例。
<link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.0/jasmine_favicon.png"> <link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.0/jasmine.css"> <script type="text/javascript" src="lib/jasmine-1.3.0/jasmine.js"></script> <script type="text/javascript" src="lib/jasmine-1.3.0/jasmine-html.js"></script>
spec 和 src 和 SpecRunner.html 是 Jasmine 的一个完整示例,用浏览器打开 SpecRunner.html,即可看到执行的结果。
基本概念
describe
describe 是 Jasmine 的全局函数,作为一个 Test Suite 的开始,它通常有 2 个参数:字符串和方法。字符串作为特定 Suite 的名字和标题。方法是包含实现 Suite 的代码。
清单 1.测试用例
describe("This is an exmaple suite", function() { it("contains spec with an expectation", function() { expect(true).toBe(true); expect(false).toBe(false); expect(false).not.toBe(true); }); });
Specs
Specs 通过调用 it 的全局函数来定义。和 describe 类似,it 也是有 2 个参数,字符串和方法。每个 Spec 包含一个或多个 expectations 来测试需要测试代码。
Jasmine 中的每个 expectation 是一个断言,可以是 true 或者 false。当每个 Spec 中的所有 expectations 都是 true,则通过测试。有任何一个 expectation 是 false,则未通过测试。而方法的内容就是测试主体。
JavaScript 的作用域的规则适用,所以在 describe 定义的变量对 Suite 中的任何 it 代码块都是可见的。
清单 2.测试用例
describe("Test suite is a function.", function() { var gVar; it("Spec is a function.", function() { gVar = true; expect(gVar).toBe(true); }); it("Another spec is a function.", function() { gVar = false; expect(gVar).toBe(false); }); });
Expectations
Expectations 是由方法 expect 来定义,一个值代表实际值。另外的匹配的方法,代表期望值。
清单 3.测试用例
describe("This is an exmaple suite", function() { it("contains spec with an expectation", function() { var num = 10; expect(num).toEqual(10); }); });
以上代码可以在附件中的 List.html 运行,结果见下图:
图 1.测试用例结果
总结
describe 方法用来组织相关的 Spec 集合。string 参数作为 Spec 集合的名字,会和其中的 Spec 连接组成 Spec 的完整名字。这样在一个大的 suite 中可以更容易找到某个 Spec。如果给它们命名适当,Specs 读起来是一个典型的 BDD 样式的句子。
Spec 是作为测试主体,Suite 是一个或多个 Spec 的集合。
describe 和 it 代码块中都是方法,可以包含任何可执行的代码来实现测试。而方法的内容就是 Suites。
常见用法
Matchers
每个 Matchers 实现一个布尔值,在实际值和期望值之间比较。它负责通知 Jasmine,此 expectation 是真或者假。然后 Jasmine 会认为相应的 spec 是通过还是失败。
任何 Matcher 可以在调用此 Matcher 之前用 not 的 expect 调用,计算负值的判断。
清单 4.测试用例
describe("The 'toBe' matcher compares with ===", function() { it("and has a positive case ", function() { expect(true).toBe(true); }); it("and can have a negative case", function() { expect(false).not.toBe(true); }); });
Included Matchers
Jasmine 有很多的 Matchers 集合。下面的例子中举例说明一些常用的 Matchers。另外当项目需要特定的判断,而没有包含在 Jasmine 的 Matchers 时,也可以通过写定制的 Matchers 来实现.
清单 5.测试用例
describe("Included matchers:", function() { it("The 'toBe' Matcher", function() { var a = 3.6; var b = a; expect(a).toBe(b); expect(a).not.toBe(null); }); describe("The 'toEqual' matcher", function() { it("works for simple literals and variables", function() { var a = "varA"; expect(a).toEqual("varA"); }); it("Work for objects", function() { var obj = { a: 1, b: 4 }; var obj2 = { a: 1, b: 4 }; expect(obj).toEqual(obj2); }); }); it("The 'toBeDefined' matcher ", function() { var obj = { defined: 'defined' }; expect(obj.defined).toBeDefined(); expect(obj.undefined).not.toBeDefined(); }); });
其他的 Matchers 还有:
toBe() toNotBe() toBeDefined() toBeUndefined() toBeNull() toBeTruthy() toBeFalsy() toBeLessThan() toBeGreaterThan() toEqual() toNotEqual() toContain() toBeCloseTo() toHaveBeenCalled() toHaveBeenCalledWith() toMatch() toNotMatch() toThrow()
Setup and Teardown
为了使某个测试用例干净的重复 setup 和 teardown 代码, Jasmine 提供了全局的 beforeEach 和 afterEach 方法。正像其名字一样,beforeEach 方法在 describe 中的
每个 Spec 执行之前运行,afterEach 在每个 Spec 调用后运行。
这里的在同一 Spec 集合中的例子有些不同。测试中的变量被定义为全局的 describe 代码块中,用来初始化的代码被挪到 beforeEach 方法中。afterEach 方法在继续前重置这些变量。
清单 6.测试用例
describe("An example of setup and teardown)", function() { var gVar; beforeEach(function() { gVar = 3.6; gVar += 1; }); afterEach(function() { gVar = 0; }); it("after setup, gVar has new value.", function() { expect(gVar).toEqual(4.6); }); it("A spec contains 2 expectations.", function() { gVar = 0; expect(gVar).toEqual(0); expect(true).toEqual(true); }); });
嵌套代码块
describe 可以嵌套, Specs 可以定义在任何一层。这样就可以让一个 suite 由一组树状的方法组成。在每个 spec 执行前,Jasmine 遍历树结构,按顺序执行每个 beforeEach 方法。Spec 执行后,Jasmine 同样执行相应的 afterEach。
清单 7.测试用例
describe("A spec", function() { var gVar; beforeEach(function() { gVar = 3.6; gVar += 1; }); afterEach(function() { gVar = 0; }); it("after setup, gVar has new value.", function() { expect(gVar).toEqual(4.6); }); it("A spec contains 2 expectations.", function() { gVar = 0; expect(gVar).toEqual(0); expect(true).toEqual(true); }); describe("nested describe", function() { var tempVar; beforeEach(function() { tempVar = 4.6; }); it("gVar is global scope, tempVar is this describe scope.", function() { expect(gVar).toEqual(tempVar); }); }); });
跳过测试代码块
Suites 和 Specs 分别可以用 xdescribe 和 xit 方法来禁用。运行时,这些 Suites 和 Specs 会被跳过,也不会在结果中出现。这可以方便的在项目中可以根据需要来禁用隐藏某些测试用例。
清单 8.测试用例
xdescribe("An example of xdescribe.", function() { var gVar; beforeEach(function() { gVar = 3.6; gVar += 1; }); xit(" and xit", function() { expect(gVar).toEqual(4.6); }); });
以上代码可以在附件中的 List.html 运行,结果见下图:
图 2.测试用例结果
与其他工具的集成
Karma
在 Java 中,用 JUnit 做单元测试, 用 Maven 进行自动化单元测试;
同样相对应的 JS 中,则可以用 Jasmine 做单元测试,用 Karma 自动化完成单元测试。
Karma 作为 JavaScript 测试执行过程管理工具,可用于测试所有主流 Web 浏览器。下面简单介绍一下 Karma 与 Jasmine 的集成。
首先,下载安装 Karma。
初始化 karma 配置文件 karma.conf.js。
安装集成包 karma-jasmine。
修改 karma.conf.js 配置文件。
需要修改:files 和 exclude 变量。其中 autoWatch 设置为 true,这样如果修改测试文件并保存后,Karma 会检测到然后自动执行。
module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine'], files: ['*.js'], exclude: ['karma.conf.js'], reporters: ['progress'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], captureTimeout: 60000, singleRun: false }); };
启动 karma,自动执行单元测试。
F:\Projects\karma>karma start karma.conf.js
另外,Jasmine 也可以与持续集成工具 Jenkins 进行集成。
一个 Jasmine 的完整例子
Jasmine 在 JavaScript 中编译,必须被包含在一个 JS 环境中,比如一个 web 网页,来运行。JavaScript 被包含,通过一个<script>标签,然后所有以上的 specs 可以通过 Jasmine 计算和记录。这样 Jasmine 可以运行所有这些 specs。此页面被认为是一个"runner"运行者。
下面通过一个具体的例子来介绍通过 runner 怎样执行 Jasmine 的 suite。
首先,创建 HTMLReporter,Jasmine 调用它,来提供每个 Spec 和 Suite 的结果。
报告负责展现结果给用户。
代理为报告过滤 specs。允许点击某个结果中的 suites 或者 specs 来只运行 suite 的子集合。
当页面完成加载时运行所有的测试-然后确认运行任何之前的 onload 句柄。
见下面清单 9,完整代码示例见附件。
清单 9 .代码示例
<script type="text/javascript"> var jasmineEnv = jasmine.getEnv(); var htmlReporter = new jasmine.HtmlReporter(); jasmineEnv.addReporter(htmlReporter); jasmineEnv.specFilter = function(spec) { return htmlReporter.specFilter(spec); }; window.onload = function() { jasmineEnv.execute(); }; </script>
完整例子可以在附件中的 Reporter.html 运行,结果见下图:
图 3.测试用例结果
结语
通过本文的介绍,我们可以了解 Jasmine 的一些基本概念和用法,为组织项目的测试打下基础,为项目代码的可靠性和稳定性提供保证,并介绍了 Jasmine 和其他框架的集成。Jasmine 的一些相对高级的用法和技巧,会在后续的文章中进行介绍。