写在前面
本文会介绍一些jasmine的基本概念和语法,并给出简单易懂的示例。适合初学jasmine者,如果你已经接触并使用过jasmine,可能并不太适合你
Jasmine 前端单元测试框架
Jasmine是面向行为驱动开发(BDD)的Javascript单元测试框架。它不依赖于其他任何javascript框架,语法清晰简单,很容易上手写出测试代码
BDD 行为驱动开发
,是一种新的敏捷开发方法。相对于TDD(测试驱动开发),它更趋向于需求,需要共同利益者的参与,强调用户故事和行为;是面向开发者、QA、非技术人员或商业参与者共同参与和理解的开发活动,而不是TDD简单地只关注开发者的方法论;
-
TDD测试驱动开发
,是一种不同于传统软件开发流程:开发结束再测试介入的新型开发方法。要求把项目按功能点划分,在编写每个功能点前先编写测试代码,然后再编写使测试通过的功能代码,通过测试来推动整个开发工作。
如果你想深入了解BDD和TDD:
可阅读下关于前端开发谈谈单元测试这篇文章,另外整理了基本书籍,推荐给大家:
- 测试驱动开发byExample
- 测试驱动开发的艺术
学习jasmine
jasmine学习环境搭建
在开始学习jasmine之前,搭建一个测试jasmine语法的学习环境是很有必要的,但实际开发中不推荐使用这样的环境
获取安装包
可以在开源社区github上下载 jasmine-standalone-2.4.1.zip;
配置安装
下载后解压.zip压缩包,得到如下的目录结构:
- lib目录下包含的jasmine的源代码,把他们(jasmine.css,jasmine.js,jasmine-html.js)引入页面中,就构造了一个jasmine的运行器(Runner);
- 开发基于jasmine的测试用例并再引入到jasmine的运行器(页面)中,就开始测试工作了。
sepc,src 和SpecRunner.html是jasmine的一个官方完整example,使用浏览器直接打开SpecRunner.html就可以看到example的测试结果
jasmine语法介绍
阅读上例SpecRunner,你应该有了关于jasmine测试用例如何去写的问题,带着问题来学习下jasmine的语法是再好不过的方式了
describe方法
describe
是jasmine用于描述测试集(Test Suite)的全局函数,通常有两个参数,一个字符串String,一个方法function;字符串用来描述Test suite,function里的代码就是测试代码,表示一个测试集合;
一个测试集合可以包含多个spec(测试点)
describe("A suite",function(){
it("contains spec with an expectation",function(){
expect(true).toBe(true);
})
})
it方法
jasmine使用it
来定义spec(测试点),it方法很像describe,同样有两个参数,一个String,一个function。String用来描述测试点(spec),function就是具体的测试代码;
一个测试点(sepc)可以包含多个expectations(断言)
expectations
断言以expect
语句来表示,返回ture或false;
expect有一个参数, 代表测试的实际值,它和表示匹配规则的Matcher链接在一起,Matcher带有期望值;
全部的断言返回true,这个测试点才通过,只要有一个断言返回false,测试点不通过
describe("A suite is just a function",function(){
var a;
var b;
it("and so is a spec",function(){
a=true;
b=false;
expect(a).toBe(true);
expect(b).toBe(true);
})
})
Matchers
Matcher实现了断言的比较操作,将expectation传入的实际值和Matcher传入的期望值进行比较,得出断言的结果true or false;
否定断言:任何Matcher都能通过在expect调用Matcher前加上 not
来现实否定断言;
describe("Included matchers:", function() {
it("The 'toBe' matcher compares with ===", function() {
var a = 12;
var b = a;
expect(a).toBe(b);
expect(a).not.toBe(null);
});
//上面的例子,比较a、b是否相等;验证a是否不是空。
it("should work for objects", function() {
var foo = {
a: 12,
b: 34
};
var bar = {
a: 12,
b: 34
};
expect(foo).toEqual(bar);
});
//上面的例子比较了两个对象是否相等
});
it("The 'toMatch' matcher is for regular expressions", function() {
var message = 'foo bar baz';
expect(message).toMatch(/bar/);
expect(message).toMatch('bar');
expect(message).not.toMatch(/quux/);
});
//也可以使用正则表达式
it("The 'toBeDefined' matcher compares against `undefined`", function() {
var a = {
foo: 'foo'
};
expect(a.foo).toBeDefined();
expect(a.bar).not.toBeDefined();
});
//验证变量是否被定义
it("The 'toBeNull' matcher compares against null", function() {
var a = null;
var foo = 'foo';
expect(null).toBeNull();
expect(a).toBeNull();
expect(foo).not.toBeNull();
});
//验证是否为空
it("The 'toBeTruthy' matcher is for boolean casting testing", function() {
var a, foo = 'foo';
expect(foo).toBeTruthy();
expect(a).not.toBeTruthy();
});
it("The 'toBeFalsy' matcher is for boolean casting testing", function() {
var a, foo = 'foo';
expect(a).toBeFalsy();
expect(foo).not.toBeFalsy();
});
//变量是否能够转化成boolean变量? 不太确定
it("The 'toContain' matcher is for finding an item in an Array", function() {
var a = ['foo', 'bar', 'baz'];
expect(a).toContain('bar');
expect(a).not.toContain('quux');
});
//是否包含
it("The 'toBeLessThan' matcher is for mathematical comparisons", function() {
var pi = 3.1415926, e = 2.78;
expect(e).toBeLessThan(pi);
expect(pi).not.toBeLessThan(e);
});
it("The 'toBeGreaterThan' is for mathematical comparisons", function() {
var pi = 3.1415926, e = 2.78;
expect(pi).toBeGreaterThan(e);
expect(e).not.toBeGreaterThan(pi);
});
//数学大小的比较
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);
});
//两个数值是否接近,这里接近的意思是将pi和e保留一定小数位数后,是否相等。(一定小数位数:默认为2,也可以手动指定)
it("The 'toThrow' matcher is for testing if a function throws an exception", function() {
var foo = function() {
return 1 + 2;
};
var bar = function() {
return a + 1;
};
expect(foo).not.toThrow();
expect(bar).toThrow();
});
});
//测试一个方法是否抛出异常
Setup和Teardown方法
为了减少重复性的代码,jasmine提供了beforeEach
、afterEach
、beforeAll
、afterAll
方法。
- beforeEach() :在describe函数中每个Spec执行之前执行;
- afterEach() :在describe函数中每个Spec执行之后执行;
- beforeAll() :在describe函数中所有的Specs执行之前执行,且只执行一次
- afterAll () : 在describe函数中所有的Specs执行之后执行,且只执行一次
describe("A spec (with setup and tear-down)", function () {
var foo;
//beforeAll 在所有的it方法执行之前执行一次
beforeAll(function () {
foo = 1;
console.log("beforeAll run");
});
//afterAll 在所有的it方法执行之后执行一次
afterAll(function () {
foo = 0;
console.log("afterAll run");
});
//beforeEach 在每个it方法执行之前都执行一次
beforeEach(function () {
console.log("beforeEach run");
});
//afterEach 在每个it方法执行之后都执行一次
afterEach(function () {
console.log("afterEach run");
});
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);
});
});
上面代码在浏览器控制台的输出
很明显
- beforeAll和afterAll在两个it执行前后,总的只执行了一遍;
- beforeEach,afterEach在每次it执行前后,都执行了一遍,所以结果打印了两遍
beforeEach run
和afterEach run
describe函数的嵌套
describe函数可以嵌套,嵌套中的每层都可以定义Specs(测试点)、beforeEach以及afterEach函数;
执行内层Spec时,会按嵌套由外到内的顺序执行每个beforeEach函数(所以内层Spec可以访问外层Spec中的beforeEach中的数据),另外当内层Spec执行完后,会按由内到外的顺序执行每个afterEach函数;
describe("A spec", function () {
var foo;
beforeAll(function () {
console.log("outer beforeAll");
});
afterAll(function () {
console.log("outer afterAll");
});
beforeEach(function () {
foo = 0;
foo += 1;
console.log("outer beforeEach");
});
afterEach(function () {
foo = 0;
console.log("outer afterEach");
});
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);
});
describe("nested inside a second describe", function () {
var bar;
beforeAll(function () {
console.log("inner beforeAll");
});
beforeEach(function () {
bar = 1;
console.log("inner beforeEach")
});
it("can reference both scopes as needed", function () {
expect(foo).toEqual(bar);
})
})
});
同样,代码在浏览器控制台有输出
自定义Matchers
自定义Matcher是一个函数,该函数返回一个闭包,该闭包实质是一个compare
函数,compare
接受2个参数:actual value(expect传入的实际值)和expected value(matcher函数传入的期望值);
compare
函数必须返回一个带pass属性的Object,pass
属性值是一个boolean值,表示Matcher的结果,换句话说,实际值和期望值比较的结果,存放在pass
属性中;
测试失败的提示信息可以通过Object的message属性来定义,如果没定义message信息返回,则会jasmine会生成一个默认的错误信息提示;
var customMatchers = {
toBeGoofy: function (util, customEqualityTesters) {
return {
compare: function (actual, expected) {
if (expected === undefined) {
expected = '';
}
console.log(util);
console.log(customEqualityTesters);
var result = {};
//比较结果true or false 通过pass 属性值返回
result.pass = util.equals(actual.hyuk, "gawrsh" + expected, customEqualityTesters);
//messge定义
if (result.pass) {
result.message = "Expected" + actual + "not to be quite so goofy";
} else {
result.message = "Expected" + actual + "to be goofy,but it was not very goofy";
}
return result;
}
};
}};
自定义Matcher构造函数接受两个参数,util:给Matcher使用的一组工具函数(equals,contains,buildFailureMessage) ;customEqualityTesters:调用util.equals时需要传入,仅此而已
自定义Matchers的使用
在定义完Matcher之后,就是使用它了。有两种使用Matcher的方法:
- 将Matcher函数添加到特定
describe
函数的beforeEach
中,方便该describe
函数中的Spec都能调用得到它。其他非嵌套describe中的Spec是无法调用到它的;
describe("Custom matcher: 'toBeGoofy'", function() {
beforeEach(function() {
jasmine.addMatchers(customMatchers);
});
it("can take an 'expected' parameter", function() {
expect({
hyuk: 'gawrsh is fun'
}).toBeGoofy(' is fun');
});
});
- 另外一种是将Matcher函数添加到全局
beforeEach
函数中,这样所有的Suites中的所有的Specs,都可以使用该Matcher。下面的例子引用自官方example
//定义
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() {
player.play(song);
//demonstrates use of custom matcher
expect(player).toBePlaying(song);
});
describe("when song has been paused", function() {
it("should indicate that the song is currently paused", function() {
// demonstrates use of 'not' with a custom matcher
expect(player).not.toBePlaying(song);
});
)};
我的jasmine gitstart
https://github.com/unnKoel/jasmine-gitstart
参考
github jasmine
关于前端开发谈谈单元测试
JavaScript单元测试框架-Jasmine
Javascript测试框架Jasmine(四):自定义Matcher
jasmine测试框架简介
JavaScript 单元测试框架:Jasmine 初探