当前位置: 代码迷 >> 综合 >> js单元测试框架 jasmine
  详细解决方案

js单元测试框架 jasmine

热度:86   发布时间:2024-01-04 08:51:38.0

一、jasmine 简介

Jasmine就是一个行动驱动开发模式的JS的单元测试工具。

Suites(describe)是Jasmine的核心,是一个测试集,里面包括多个specs(it),而每个specs里面可能包含多个断言(expect)。

  • jasmine.js:整个框架的核心代码。
  • jasmine-html.js:用来展示测试结果的js文件。
  • boot.js:jasmine框架的的启动脚本。需要注意的是,这个脚本应该放在jasmine.js之后,自己的js测试代码之前加载。 
  • jasmine.css:用来美化测试结果。

在html页面引入以上文件,则可以进行jsamine测试

player.js为需要测试的文件

function Player() {
}
Player.prototype.play = function(song) {this.currentlyPlayingSong = song;this.isPlaying = true;
};Player.prototype.pause = function() {this.isPlaying = false;
};Player.prototype.resume = function() {if (this.isPlaying) {throw new Error("song is already playing");}this.isPlaying = true;
};Player.prototype.makeFavorite = function() {this.currentlyPlayingSong.persistFavoriteStatus(true);
};

playerspec.js   针对Player.js所写的测试用例

describe("Player", function() {var player;var song;beforeEach(function() {player = new Player();song = new Song();});it("should be able to play a Song", function() {player.play(song);expect(player.currentlyPlayingSong).toEqual(song);//demonstrates use of custom matcherexpect(player).toBePlaying(song);});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 matcherexpect(player).not.toBePlaying(song);});it("should be possible to resume", function() {player.resume();expect(player.isPlaying).toBeTruthy();expect(player.currentlyPlayingSong).toEqual(song);});});// demonstrates use of spies to intercept and test method callsit("tells the current song if the user has made it a favorite", function() {spyOn(song, 'persistFavoriteStatus');player.play(song);player.makeFavorite();expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true);});//demonstrates use of expected exceptionsdescribe("#resume", function() {it("should throw an exception if song is already playing", function() {player.play(song);expect(function() {player.resume();}).toThrowError("song is already playing");});});
});

核心概念

1、describe("Player", function() {})

Suite表示一个测试集,以函数describe(string, function)封装,它包含2个参数:
string:测试组名称,
function:测试组函数。

一个Suite(describe)包含多个Specs(it),一个Specs(it)包含多个断言(expect)。

2、Setup和Teardown操作/4个全局函数

为了能够在测试开始前先进行部分初始化,或者在测试结束后对一些内容进行销毁,主体还包括四个全局函数:
beforeEach():在describe函数中每个Spec执行之前执行。
afterEach():在describe函数中每个Spec数执行之后执行。
beforeAll():在describe函数中所有的Specs执行之前执行,但只执行一次,在Sepc之间并不会被执行。
afterAll():在describe函数中所有的Specs执行之后执行,但只执行一次,在Sepc之间并不会被执行。
上例中即在beforeEach()中new 了一个构造函数。

3、 it("should be able to play a Song", function() {})

Spec表示测试用例,以it(string, function)函数封装,它也包含2个参数:
string:测试用例名称,
function:测试用例函数。

4、expect(player.currentlyPlayingSong).toEqual(song);

Expectation就是一个断言,以expect语句表示,返回truefalseexpect语句有1个参数,代表要测试的实际值(the actual)。

只有当一个Spec中的所有Expectations全为ture时,这个Spec才通过,否则失败。

5、 expect(player.isPlaying).toBeFalsy();

Matcher实现了断言的比较操作,将Expectation传入的实际值和Matcher传入的期望值比较。
任何Matcher都能通过在expect调用Matcher前加上not来实现一个否定的断言(expect(a).not().toBe(false);)。

常用的Matchers有:

  • toBe():相当于===比较。
  • toNotBe()
  • toBeDefined():检查变量或属性是否已声明且赋值。
  • toBeUndefined()
  • toBeNull():是否是null
  • toBeTruthy():如果转换为布尔值,是否为true
  • toBeFalsy()
  • toBeLessThan():数值比较,小于。
  • toBeGreaterThan():数值比较,大于。
  • toEqual():相当于==,注意与toBe()的区别。
    一个新建的Object不是(not to be)另一个新建的Object,但是它们是相等(to equal)的。
expect({}).not().toBe({});
expect({}).toEqual({});
  • toNotEqual()
  • toContain():数组中是否包含元素(值)。只能用于数组,不能用于对象。
  • toBeCloseTo():数值比较时定义精度,先四舍五入后再比较。
  it("The 'toBeCloseTo' matcher is for precision math comparison", function() {var pi = 3.1415926,e = 2.78;expect(pi).not.toBeCloseTo(e, 2);expect(pi).toBeCloseTo(e, 0);});

6、 expect(player).toBePlaying(song);

  • toHaveBeenCalled()
  • toHaveBeenCalledWith()
  • toMatch():按正则表达式匹配。
  • toNotMatch()

 注意到在player.js中并没有toBeplaying()这一功能函数,这是一个自定义函数。

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

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

最后测试输出的失败信息应该在返回结果Object中的message属性中来定义。

beforeEach(function () {jasmine.addMatchers({toBePlaying: function () {return {compare: function (actual, expected) {var player = actual;return {pass: player.currentlyPlayingSong === expected && player.isPlaying};}};}});
});

对于本示例来说: actual为player,expected 为son

分析pass 属性的值:player.currentlyPlayingSong为song ===song &&前为true。player.isPlaying 在调用player.play(song)时,this.isPlaying被处置为true。所以Pass属性值为true。

7、禁用suites

Suites可以被Disabled。在describe函数名之前添加x即可将Suite禁用。

8、挂起Spec

有3种方法可以将一个Spec标记为Pending。被Pending的Spec不会被执行,但是Spec的名字会在结果集中显示,只是标记为Pending。

  • 如果在Spec函数it的函数名之前添加xxit),那么该Spec就会被标记为Pending。
  • 一个没有定义函数体的Sepc也会在结果集中被标记为Pending。
  • 如果在Spec的函数体中调用pending()函数,那么该Spec也会被标记为Pending。pending()函数接受一个字符串参数,该参数会在结果集中显示在PENDING WITH MESSAGE:之后,作为为何被Pending的原因。
 xit("can be declared 'xit'", function() {expect(true).toBe(false);});it("can be declared with 'it' but without a function");it("can be declared by calling 'pending' in the spec body", function() {expect(true).toBe(false);pending('this is why it is pending');});

9、全局匹配谓词

名称 用法

jasmine.any

参数为一个构造函数,用于检测该参数是否与实际值所对应的构造函数相匹配。

jasmine.anything

用于检测实际值是否为nullundefined,如果不为nullundefined,则返回true

jasmine.objectContaining

用于检测实际Object值中是否存在特定key/value对。

jasmine.arrayContaining

用于检测实际Array值中是否存在特定值。
 expect({}).toEqual(jasmine.any(Object));expect(12).toEqual(jasmine.any(Number));expect(1).toEqual(jasmine.anything());

10、异步支持(Asynchronous Support)

调用beforeEachit或者afterEach时,可以添加一个可选参数(Function类型,在官方文档的例子中该参数为done)。当done函数被调用,表明异步操作的回调函数调用成功;否则如果没有调用done,表明异步操作的回调函数调用失败,则该Spec不会被调用,且会因为超时退出。

二、jasmine实例

describe('Take out food', function () {it('should generate best charge when best is 指定菜品半价', function() {let inputs = ["ITEM0001 x 1", "ITEM0013 x 2", "ITEM0022 x 1"];let summary = bestCharge(inputs).trim();let expected = `
============= 订餐明细 =============
黄焖鸡 x 1 = 18元
肉夹馍 x 2 = 12元
凉皮 x 1 = 8元
-----------------------------------
使用优惠:
指定菜品半价(黄焖鸡,凉皮),省13元
-----------------------------------
总计:25元
===================================`.trim();expect(summary).toEqual(expected);});it('should generate best charge when best is 满30减6元', function() {let inputs = ["ITEM0013 x 4", "ITEM0022 x 1"];let summary = bestCharge(inputs).trim();let expected = `
============= 订餐明细 =============
肉夹馍 x 4 = 24元
凉皮 x 1 = 8元
-----------------------------------
使用优惠:
满30减6元,省6元
-----------------------------------
总计:26元
===================================`.trim();expect(summary).toEqual(expected)});it('should generate best charge when no promotion can be used', function() {let inputs = ["ITEM0013 x 4"];let summary = bestCharge(inputs).trim();let expected = `
============= 订餐明细 =============
肉夹馍 x 4 = 24元
-----------------------------------
总计:24元
===================================`.trim()expect(summary).toEqual(expected)});});

 

  相关解决方案