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.jsPlayer.js
/spec 下则包含了对应的测试代码,PlayerSpec.jsSpecHelper.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
 
posted @ 2016-03-18 16:58  赵小吉  阅读(1179)  评论(0编辑  收藏  举报