jasmine行为驱动,测试先行

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-jasmine-bdd

jasmine

前言
TDD(Test Driven Development)测试驱动开发,是敏捷开发中提出的最佳实践之一。jasmine很有意思的提出了BDD(Behavior Driven Development)行为驱动开发,诱发了我的好奇心,一探究竟。

测试驱动开发,对软件质量起到了规范性的控制。未写实现,先写测试,一度成为Java领域研发的圣经。随着Javascript兴起,功能越来越多,代码量越来越大,开发人员素质相差悬殊,真的有必要建立对代码的规范性控制。jasmine就是为团队合作而生。

目录

  1. jasmine介绍
  2. jasmine安装
  3. jasmine环境配置
  4. jasmine使用

1. jasmine介绍

Jasmine是一个用来编写Javascript测试的框架,它不依赖于任何其它的javascript框架,也不需要对DOM。它有拥有灵巧而明确的语法可以让你轻松的编写测试代码。

jasmine的结构很简单:


describe("A suite", function() {
  var foo;
  beforeEach(function() {
    foo = 0;
    foo += 1;
  });

  afterEach(function() {
    foo = 0;
  });

  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});

每个测试都在一个测试集中运行,Suite就是一个测试集,用describe函数封装。 Spec表示每个测试用例,用it函数封装。通过expect函数,作为程序断言来判断相等关系。setup过程用beforeEach函数封装,tearDown过程用afterEach封装。

用过Java中JUnit的同学,看到这种对应关系,应该就会明白jasmine就是JUnit的Javascript重写版。

2. jasmine安装

安装jasmine类库时,让我们利用bower,一键搞定。bower的使用介绍,请参考文章:bower解决js的依赖管理。把已经学到的知识用起来,效率马上提升10倍。


~ D:\workspace\javascript>mkdir jasmine
~ D:\workspace\javascript>cd jasmine

#查找jasmine项目
~ D:\workspace\javascript\jasmine>bower search jasmine
-----------------------------------------
Update available: 1.2.4 (current: 1.1.2)
Run npm update -g bower to update
-----------------------------------------

Search results:

    jasmine git://github.com/pivotal/jasmine.git
    jasmine-jquery git://github.com/velesin/jasmine-jquery
    jasmine-sinon git://github.com/froots/jasmine-sinon.git
    jasmine-ajax git://github.com/pivotal/jasmine-ajax.git
    jasmine.async git://github.com/derickbailey/jasmine.async.git
    flight-jasmine git://github.com/twitter/flight-jasmine.git
    jasmine-jstd-adapter git://github.com/ibolmo/jasmine-jstd-adapter.git
    jasmine-flight git://github.com/flightjs/jasmine-flight.git
    jasmine-signals git://github.com/AdamNowotny/jasmine-signals.git
    jasmine-underscore git://github.com/adscott/jasmine-underscore.git
    jasmine-bootstrap git://github.com/esbie/jasmine-bootstrap.git
    jasmine-data-provider git://github.com/sublimino/jasmine-data-provider.git
    jasmine-sugar git://github.com/fantactuka/jasmine-sugar.git

#安装jasmine类库
~ D:\workspace\javascript\jasmine>bower install jasmine
bower jasmine#*             not-cached git://github.com/pivotal/jasmine.git#*
bower jasmine#*                resolve git://github.com/pivotal/jasmine.git#*
bower jasmine#*               download https://github.com/pivotal/jasmine/archive/v1.3.1.tar.gz
bower jasmine#*                extract archive.tar.gz
bower jasmine#*               resolved git://github.com/pivotal/jasmine.git#1.3.1
bower jasmine#~1.3.1           install jasmine#1.3.1

jasmine#1.3.1 bower_components\jasmine

3. jasmine环境配置

jasmine运行需要4个部分:

  • 1. 运行时环境:我们这里基于chrome浏览器,通过HTML作为javascript载体
  • 2. 源文件:用于实现某种业务逻辑的文件,就是我们平时写的js脚本
  • 3. 测试文件:符合jasmineAPI的测试js脚本
  • 4. 输出结果:jasmine提供了基于网页的输出结果

下面让我开始jasmine:

1). 新建一个HTML文件:test.html

~ vi test.html

<!DOCTYPE html>
<html>
<head>
<title>jasmine test</title>
<link rel="stylesheet" type="text/css" href="bower_components/jasmine/lib/jasmine-core/jasmine.css">
<script type="text/javascript" src="bower_components/jasmine/lib/jasmine-core/jasmine.js"></script>
<script type="text/javascript" src="bower_components/jasmine/lib/jasmine-core/jasmine-html.js"></script>
</head>
<body>
<h1>jasmine test</h1>
<script type="text/javascript" src="src.js"></script>
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript" src="report.js"></script>
</body>
</html>

我们看到页面上面有5个javascript的导入:

  • jasmine.js:核心文件用于执行单元测试的类库
  • jasmine-html.js:用于显示单元测试的结果的类库
  • src.js:我们自己的业务逻辑的js
  • test.js:单元测试的js
  • report.js:用于启动单元测试js

jasmine.js,jasmine-html.js是系统类库,直接引入。
report.js,是启动脚本,写法是固定的。


~ vi report.js

(function() {
    var jasmineEnv = jasmine.getEnv();
    jasmineEnv.updateInterval = 1000;

    var htmlReporter = new jasmine.HtmlReporter();

    jasmineEnv.addReporter(htmlReporter);

    jasmineEnv.specFilter = function(spec) {
        return htmlReporter.specFilter(spec);
    };

    var currentWindowOnload = window.onload;

    window.onload = function() {
        if (currentWindowOnload) {
            currentWindowOnload();
        }
        execJasmine();
    };

    function execJasmine() {
        jasmineEnv.execute();
    }

})();

src.js是实现业务逻辑的文件,我们定义sayHello的函数


~ vi src.js

function sayHello(name){
    return "Hello " + name;
}

test.js对源文件进行单元测试


~ vi test.js

describe("A suite of basic functions", function() {
    var name;

    it("sayHello", function() {
        name = "Conan";
        var exp = "Hello Conan";
        expect(exp).toEqual(sayHello(name));
    });
});

页面报告截图:
test1

我们完成了,最基础的jasmine功能。

4. jasmine使用

1). 测试先行
测试先行,就是未写现实,先写用例。

我们刚才已经配置好了jasmine的环境,后面我们所有功能代码都写在src.js中,测试代码都写在test.js中。

有一个需求,要实现单词倒写的功能。如:”ABCD” ==> “DCBA”

我们编辑test.js,增加一个测试用例


~ vi test.js

it("reverse word",function(){
    expect("DCBA").toEqual(reverse("ABCD"));
});

执行这个页面,单元测试失败,提示没有reverse函数。
test2

编辑src.js,增加reverse函数


function reverse(name){
    return "DCBA";
}

执行页面,单元测试成功!
test3

单元测试成功,是不是能说明我们完成了“单词倒写”的功能呢?答案是不确定的。如果想保证功能是正确性,我们需要进行更多次的验证。

编辑test.js,继续增加测试用例


    it("reverse word",function(){
        expect("DCBA").toEqual(reverse("ABCD"));
        expect("Conan").toEqual(reverse("nanoC"));
    });

刷新页面,又提示单元测试失败,因为我们希望输入是”Conan”,输出是”nanoC”,但是功能代码返回是”DCBA”,显然业务逻辑写的是不对的。

修改src.js,修改reverse函数


~ vi src.js

function reverse(name){
    return name.split("").reverse().join("");
}

再次刷新页面,单元测试成功!!

这是敏捷开发中提倡的“测试先行”的案例,对于产品级的代码,我们真的应该要高质量控制。

2). jasmine语法实践
以下内容是对jasmine语法的介绍,都在test.js中实现。

做一个嵌套的describe(“A suite of jasmine’s function”)

对断言表达式的使用


describe("A suite of jasmine's function", function() {
    describe("Expectations",function(){
        it("Expectations",function(){
            expect("AAA").toEqual("AAA");
            expect(52.78).toMatch(/\d*.\d\d/);
            expect(null).toBeNull();
            expect("ABCD").toContain("B");
            expect(52,78).toBeLessThan(99);
            expect(52.78).toBeGreaterThan(18);

            var x = true;
            var y;
            expect(x).toBe(true);
            expect(x).toBeDefined();
            expect(y).toBeUndefined();
            expect(x).toBeTruthy();
            expect(!x).toBeFalsy();

            var fun = function() { return a + 1;};
            expect(fun).toThrow();
        });
    });
});

对开始前和使用后的变量赋值


    describe("Setup and Teardown",function(){
        var foo;
        beforeEach(function() {
            foo = 0;
            foo += 1;
        });
        afterEach(function() {
            foo = 0;
        });

        it("is just a function, so it can contain any code", function() {
            expect(foo).toEqual(1);
        });

        it("can have more than one expectation", function() {
            expect(foo).toEqual(1);
            expect(true).toEqual(true);
        });
    });

对忽略suite的声明


   xdescribe("Disabling Specs and Suites", function() {
        it("Disabling Specs and Suites",function(){
            expect("AAA").toEqual("AAA");
        });
    });

对异步程序的单元测试


   describe("Asynchronous Support",function(){
        var value, flag;
        it("Asynchronous Support", function() {
            runs(function() {
                flag = false;
                value = 0;
                setTimeout(function() {
                    flag = true;
                }, 500);
            });
            waitsFor(function() {
                value++;
                return flag;
            }, "The Value should be incremented", 750);

            runs(function() {
                expect(value).toBeGreaterThan(0);
            });
        });
    });

我们成功地对Javascript完成各种的单元测试,下面是测试报告。
test4

最后,BDD其实就是TDD。所以,不要被新名词吓到,实质是一样的。

转载请注明出处:
http://blog.fens.me/nodejs-jasmine-bdd

打赏作者

This entry was posted in Javascript语言实践

0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

18 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Je

非常好。一直想学习使用jasmine ,找了一些资料,看的云里雾里,看了你的文章豁然开朗。
顺便问个问题,nodejs 除了forever 还有哪几个与它功能相似的东西?

Conan Zhang

PM2, upstart

upstart封装nodejs应用为系统服务:
http://blog.fens.me/linux-upstart-nodejs/

[…] Nodejs领域: Jasmine做单元测试,Karma自动化完成单元测试,Grunt启动Karma统一项目管理,Yeoman最后封装成一个项目原型模板,npm做nodejs的包依赖管理,bower做javascript的包依赖管理。Java领域:JUnit做单元测试, Maven自动化单元测试,统一项目管理,构建项目原型模板,包依赖管理。 […]

micfan

@Conan, I want to know how to startup jasmine in cli. Is it by integrated in Karma, thx?

Conan Zhang

Hi micfan,

startup jasmine in cli
===========================
You need check the Jasmine document online.

integrate with Karam:
==========================
Karma和Jasmine自动化单元测试
http://blog.fens.me/nodejs-karma-jasmine/

micfan

Thanks, jasmine is a tool, so it require a runner or driver, such as Karma, Grunt, etc.

Conan Zhang

什么情况?评论禁止灌水!

杨超 路飞

楼主, 我按照你的代码弄,最后浏览器里 说 jasmine is not defined ,这是为什么?
report.js的 var jasmineEnv = jasmine.getEnv(); 这个地方,还有 test.js里的describe(“A suite of basic functions”, function () 的 describe is not defined

杨超 路飞

问题定位了,我用了最新的 jasmine 2.0 导致的 ,哎,真是伤心

Conan Zhang

技术更新太快了,文章还没有半年,就不一样!呵呵

king

问题同“杨超”,使用jasmine 2.0版本的会报错,看官网,原来2.0后用户不用再自己写启动文件了, 在jasmine的lib目录下自带有一个启动文件boot.js,替换博主自己写的report.js就可以了。

AbnerZheng

jasmine2.1.2 实测有效

Conan Zhang

Good!

[…] 测试:jasmine,karma […]

jixinz

npm不是就能实现包管理吗 还要bower干什么 不解 比如要jquery的话可以npm install jquery 然后jquery就在当前目录的node_modules下面了 我接触node js 不久 也许这个问题在高手看来比较小白 还请赐教

Conan Zhang

npm是管理Nodejs项目的, bower是管理纯js项目的。

Tong

jasmine2.4需要boot.js

Conan Zhang

本文写的太早了,一直没有更新。:-)

18
0
Would love your thoughts, please comment.x
()
x