Jasmine Introduction —— Jasmine 测试框架初探
Jasmine 的介绍在网上随处可见,在此我们先摘录官网对其的描述:
Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.
Jasmine 是一款 BDD(Behaviour-drive development) 的JavaScript 测试框架。它独立于其他的 JS 框架自成一体,且并不需要 DOM 作为支撑。语法简单,书写容易也是其优势之一。
Jasmine 的实用性、易用性已经在大量的实际使用的中得到了验证,所以本文不再赘述。这里我们将重点放在:怎样从零开始使用 Jasmine 测试我们现存的 JavaScript 代码。
在浏览器中运行 Jasmine 测试框架:
首先,我们可以在 https://github.com/jasmine/jasmine/releases 下载到最新发布的 Jasmine standalone distribution。
以我下载的 Jasmine-standalone-2.4.1 为例,解压后文件目录结构如下,可以看到里面已经有一些示例代码:
其中 /lib 文件夹下包含了测试框架的源码等文件。
/src 下包含了待测试的 JS 代码。里面包含两个文件 Song.js 和 Player.js
/spec 下则包含了对应的测试代码,PlayerSpec.js 和 SpecHelper.js
直接在浏览器中打开 SpecRunner.html,就可以看到如下页面,
表示 SpecRunner.html 中包含的 5 个测试用例都顺利通过了。
本文中,我们不再对这部分代码进行研究,而是直接创建我们自己的测试用例。在 /src 下添加 Hello.js
var Hello = function() {}; Hello.prototype.foo = "foo"; Hello.prototype.bar = null ; Hello.prototype.helloWorld = function() { return "Hello World!"; } Hello.prototype.helloSomeone = function(toGreet) { return this.sayHello() + " " + toGreet; } Hello.prototype.sayHello = function() { return "Hello"; }
在 /spec 下添加 Hello.matcher.js
beforeEach(function () { jasmine.addMatchers({ toContainWord: function () { return { compare: function (actual, expected) { var result = {}; result.pass = (actual.indexOf(expected) !== -1); if( result.pass ) { result.message = "Expected " + actual + " to contain " + expected + "."; } else { result.message = "Expected " + actual + " to contain " + expected + ", but it does not."; } return result; } } } }); });
然后写下我们自己的测试 Hello.spec.js:
describe("Hello", function () { var hello; beforeEach(function () { hello = new Hello(); }); it("a newly created Hello instance should not be the same instance with the origin one", function () { expect(hello).not.toBe(new Hello()); expect(hello).toEqual(new Hello()); }); describe("helloWorld function", function () { it("should return hello statement", function () { expect(hello.helloWorld()).toBe("Hello World!"); }); it("should contain word 'World'", function () { expect(hello.helloWorld()).toContainWord("World!"); }); it("an undefined variable should pass 'toBeUndefined' matcher", function () { expect(hello.a).toBeUndefined(); }); it("a null variable should pass 'toBeNull' matcher", function () { expect(hello.bar).toBeNull(); }); it("variable after boolean casting should pass 'toBeTruthy' 'toBeFalsy' matcher", function () { expect(hello.foo).toBeTruthy(); expect(hello.bar).toBeFalsy(); }); it("should pass the 'toMatch' matcher for regular expressions", function (){ expect(hello.helloWorld()).toMatch(/^\w*\s\w*!$/); }); }); describe("helloSomeone function", function () { it("should calls the sayHello() function", function () { spyOn(hello, "sayHello"); hello.helloSomeone("Chou"); expect(hello.sayHello).toHaveBeenCalled(); expect(hello.sayHello).toHaveBeenCalledTimes(1); }); it("should greet the 'World'", function () { spyOn(hello, "helloSomeone"); hello.helloSomeone("World"); expect(hello.helloSomeone).toHaveBeenCalledWith("World"); expect(hello.helloSomeone).not.toHaveBeenCalledWith("world"); }); it("should calls the fake sayHello()", function () { hello.sayHello = jasmine.createSpy("'sayHello' spy"); hello.helloSomeone("world"); expect(hello.sayHello).toHaveBeenCalled(); }); }); });
第一步是 describe 我们的测试。一个测试是由 Jasmin 中的 describe function 开始的。describe 需要两个参数, 一个 String 和一个 function。在这个 String 中我们可以描述测试的名字和意图,通常是对被测试对象的描述。funciton 中则包含我们具体的测试代码。
It function, 也就是 ‘Spec’,也和describe 一样,含有两个参数,一个String和一个function。String具体描述这个测试单元的目的,在本例中,我们使用 Should do sth. when sth. happened 的格式,以使测试单元看起来更整洁也更便于理解。
一个 spec 中会包含一或多个 expectation。 Jasmine 中的 expectation 就像 JUnit 中的 assertion 一样,会对判断条件进行判断并返回 true/fase,只有当一个 spec 中所有的 expectation 全部通过时,我们才可以称之为一个 passing spec。
然后是 Matchers。每一个matcher都会比较实际输入的值,与期望的值,并返回一个 boolean。根据matchers 返回的Boolean值,jasmine决定是否让一个spec顺利通过。每一个matcher都可以通过在前面插入一个 not 声明,来实现一个相反的assertion。
在本例中,我们使用了几种常见的 Matcher: toBe toEqual toMatch toBeDefined toBeTruthy toBeNull toContain。也使用了一个自定义的 Matcher ‘toContainWord’。这个 Matcher 检查我们待判断 String 是否包含传入的参数。如
expect(“Hello World!”).toContainWord(“World!”)
就能够顺利通过 Matcher 的判断。
同其他测试框架一样,为了减少代码重复,jasmine也提供了一些全局方法来进行 setup/teardown
分别为 beforeEach, afterEach, beforeAll, afterAll。
其中 beforeAll 会在所有 spec 运行前执行一次,afterAll 则会在所有 spec 运行完成后执行一次。在 beforeAll 和afterAll中我们可以做一些成本比较高的操作,以提高我们的测试效率。不过这样做同时也会提高风险,不同 spec 之间可能会互相影响而导致测试失败。所以使用时需要多加小心。
不同于 beforeAll 和 afterAll, beforeEach 和 afterEach 则会在每个 spec 前后分别执行。
所以这些方法执行流程大体如下:
beforeAll
beforeEach
spec
afterEach
beforeEach
spec
afterEach
afterAll
做完以上工作,我们就成功的建立了自己的第一个 Jasmine 测试了
接下来就是修改 SpecRunner.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Jasmine Spec Runner v2.4.1</title> <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.4.1/jasmine_favicon.png"> <link rel="stylesheet" href="lib/jasmine-2.4.1/jasmine.css"> <script src="lib/jasmine-2.4.1/jasmine.js"></script> <script src="lib/jasmine-2.4.1/jasmine-html.js"></script> <script src="lib/jasmine-2.4.1/boot.js"></script> <!-- include source files here... --> <script src="src/Hello.js"></script> <!-- include spec files here... --> <script src="spec/Hello.matcher.js"></script> <script src="spec/Hello.spec.js"></script> </head> <body> </body> </html>
在 SpecRunner.html 中,务必要先引入所有 jasmine 的依赖文件:
jasmine.css
jasmine.js
jasmine-html.js
boot.js
再引入我们待测试的 Hello.js,最后则是我们的测试文件 Hello.matcher.js 和 Hello.spec.js。
做好以上的步骤,我们再打开 SpecRunner.html:
就能看到我们新增的 10 个测试全部通过啦。
希望本文能对广大第一次接触 Jasmine 的朋友们有所帮助,有何问题也请多多指教。
如果你希望获取更多更详细的介绍,请参考官方文档:http://jasmine.github.io/2.4/introduction.html