初识前端测试(jasmine)

JavaScript 作为一种脚本语言,和传统编程语言 C++、Java 等相比,没有诸如 Eclipse、Visual Studio 等集成开发调试环境,其调试和测试是对开发人员都是一项挑战。
目前 JS 单元测试框架有丰富的选择,比如 Buster.js、TestSwarm、JsTestDriver 等。而 Jasmine 作为流行的 JavaScript 测试工具,很轻巧只有 20K 左右,而功能丰富,让我们可以容易的写出清晰简洁的针对项目的测试用例。对基于 JavaScript 开发的项目来说,是一款不错的测试框架选择。

搭建测试环境

为了方便学习,所以我在Node.js环境下搭建一个测试环境。

> npm install --save-dev jasmine
> npm install -g jasmine  //全局安装
> ln -s /home/luo/mysolf/node/bin/jasmine /usr/local/bin/jasmine  //建立软链接
> jasmine -v

jasmine v2.8.0
jasmine-core v2.8.0

ok, 我们已经在系统上安装好了jasmine了。现在我们创建一个项目来开始尝试测试。

> mkdir jasmine&cd jasmine
> jasmine init // 初始化jasmine项目
> jasmine examples // 初始化一个实例
> jasmine // 执行测试

Started
.....


5 specs, 0 failures
Finished in 0.027 seconds

测试了5个expectations,没有错误,说明测试已经通过。

当前的项目目录如下

|-jasmine
    |-lib                  // 需要测试的项目
        |-jasmine_examples
            Player.js
            Song.js
    |-spec                 // 测试文件
        |-helpers
            |-jasmine_examples
                SpecHelper.js // 自定义Matcher
        |-jasmine_examples
            PlayerSpec.js
        |-support
            jasmine.json // 配置

基本概念

describe

先看一下spec/jasmine_examples/PlayerSpec.js测试文件。

describe("Player", function() {
    var Player = require('../../lib/jasmine_examples/Player');
    var Song = require('../../lib/jasmine_examples/Song');
    var player;
    var song;
    ......
}

describe 是 Jasmine 的全局函数,作为一个测试集的开始,它通常有 2 个参数:字符串和方法。字符串作为特定 集 的名字和标题。方法是包含实现 测试集 的代码。

Specs

describe("Player", function() {
    ...
    var song;
    it("should be able to play a Song", function() {
        player.play(song);
        ...
    });
}

Specs 通过调用 it 的全局函数来定义。和 describe 类似,it 也是有 2 个参数,字符串和方法。每个 Spec 包含一个或多个 expectations 来测试需要测试的代码。

JavaScript 的作用域的规则适用,所以在 describe 定义的变量对 测试集 中的任何 it 代码块都是可见的。

expectations

describe("Player", function() {
    ...
    it("should be able to play a Song", function() {
        player.play(song);
        expect(player.currentlyPlayingSong).toEqual(song);

        //demonstrates use of custom matcher
        expect(player).toBePlaying(song);
    });
}

Expectations 是由方法 expect 来定义,一个值代表实际值。另外的匹配的方法,代表期望值。
Jasmine 中的每个 expectation 是一个断言,可以是 true 或者 false。当每个 Spec 中的所有 expectations 都是 true,则通过测试。有任何一个 expectation 是 false,则未通过测试。而方法的内容就是测试主体。

Matchers

每个 Matchers 实现一个布尔值,在实际值和期望值之间比较。它负责通知 Jasmine,此 expectation 是真或者假。然后 Jasmine 会认为相应的 spec 是通过还是失败。
何 Matcher 可以在调用此 Matcher 之前用 not 的 expect 调用,计算负值的判断,如:

describe("Player", function() {
    ...
    it("should be able to play a Song", function() {
        player.play(song);
        expect(player.currentlyPlayingSong).not.toEqual(song);

    });
}

Jasmine 已经有很多的 Matchers 集合。

  • toBe() 相当于 ===
  • toNotBe()
  • toBeDefined() 检查变量或属性是否已声明且赋值
  • toBeUndefined()
  • toBeNull() 是否是null
  • toBeTruthy() 如果转换为布尔值,是否为true
  • toBeFalsy()
  • toBeLessThan() 数值比较,小于
  • toBeGreaterThan() 数值比较,大于
  • toEqual() 相当于 ==
  • toNotEqual()
  • toContain() 数组中是否包含元素(只能用于数组)
  • toBeCloseTo() 数值比较时定义精度(先四舍五入后再比较)
  • toHaveBeenCalled()
  • toHaveBeenCalledWith()
  • toMatch() 按正则表达式匹配
  • toNotMatch()
  • toThrow() 检验一个函数是否会抛出一个错误

当然,若上面的集合还不满足我们的需求,我们还可以自定义我们的Matchers。

自定义Matcher

自定义Matcher(被称为Matcher Factories)实质上是一个函数(该函数的参数可以为空),该函数返回一个闭包,该闭包的本质是一个compare函数,compare函数接受2个参数:actual value 和 expected value。

compare函数必须返回一个带pass属性的结果Object,pass属性是一个Boolean值,表示该Matcher的结果(为true表示该Matcher实际值与预期值匹配,为false表示不匹配),也就是说,实际值与预期值具体的比较操作的结果,存放于pass属性中。

最后测试输出的失败信息应该在返回结果Object中的message属性中来定义。
见例子中的SpecHelper.js文件

//定义
beforeEach(function () {
    jasmine.addMatchers({
        toBePlaying: function () {
        return {
            compare: function (actual, expected) {
            var player = actual;

            return {
                pass: player.currentlyPlayingSong === expected && player.isPlaying
            }
            }
        };
        }
    });
});
// 使用
describe("Player", function() {
    ...
    it("should be able to play a Song", function() {
        ......
        //demonstrates use of custom matcher
        expect(player).toBePlaying(song);
    });
}

Setup and Teardown

构建和卸载,即使用 beforeEach 和 afterEach方法。
当开始一个测试集describe时,我们可以先使用beforeEach来执行为我们即将测试的用例的做一个当前的程序环境构建,因为,测试用例通常是在某些条件下执行的,这样才能使我们的测试有意义。而afterEach用于测试集测试结束后,做的一些操作,例如前置已经改变的变量。

describe("Player", function() {
    ...
    var song;
    beforeEach(function() {
        player = new Player();
        song = new Song();
    });
    it("should be able to play a Song", function() {
        ......
    });
}

嵌套代码块

describe是可以进行嵌套的,Specs 可以定义在任何一层。这样就可以让一个 suite 由一组树状的方法组成。在每个 spec 执行前,Jasmine 遍历树结构,按顺序执行每个 beforeEach 方法。Spec 执行后,Jasmine 同样执行相应的 afterEach。

describe("Player", function() {
    ...
    var song;
    beforeEach(function() {
        player = new Player();
        song = new Song();
    });
    it("should be able to play a Song", function() {
        ......
    });
    describe("when song has been paused", function() {
        beforeEach(function() {
            player.play(song);
            player.pause();
        });
        it("should indicate that the song is currently paused", function() {
            expect(player.isPlaying).toBeFalsy();

            // demonstrates use of 'not' with a custom matcher
            expect(player).not.toBePlaying(song);
        });
    }
}

跳过测试代码块

其实这相当于注释掉我们的describe或it。只要使用 xdescribe 和 xit 方法来禁用。运行时,这些 Suites 和 Specs 会被跳过,也不会在结果中出现。这可以方便的在项目中可以根据需要来禁用隐藏某些测试用例。

xdescribe("Player", function() {
    ...
    xit("should be able to play a Song", function() {
        player.play(song);
        expect(player.currentlyPlayingSong).not.toEqual(song);

    });
}

其他工具的集成

Karma

在 Java 中,用 JUnit 做单元测试, 用 Maven 进行自动化单元测试。
同样相对应的 JS 中,则可以用 Jasmine 做单元测试,用 Karma 自动化完成单元测试。
我们可以使用这个集成karna-jasmine
我们需要安装karma 和karma-chrome-launcher

npm install -g karma
// 作软链接
git clone https://github.com/karma-runner/karma-jasmine&cd karma-jasmine //进入这个实例
npm install karma-chrome-launcher --save-dev // 安装karma-chrome-launcher

我这里只安装了karma-chrome-launcher,而这个实例还用了karma-firefox-launcher。所以在karma.conf.js把karma-firefox-launcher删除即可

karma start // 启动karma测试

最后

通过本文的介绍,了解 Jasmine 的一些基本概念和用法,为组织项目的测试打下基础,为项目代码的可靠性和稳定性提供保证,并介绍了 Jasmine 和其他框架的集成。Jasmine 的一些相对高级的用法和技巧,再叙。