前言
实习期间接触很多新东西。最近又在学习如何写一个独立的测试单元。看了公司前人的代码,后来网上搜寻了相关的知识,打算还是将自己的理解简单写下来。
简介
Jasmine是一种JavaScript的测试框架,它不依赖于其他框架,也不依赖于DOM结构。其语法特点在于简单清晰,就算是刚入门没多久的JavaScript程序猿也能够写出简单的测试单元代码。
下载 & 部署
下载地址:https://github.com/jasmine/jasmine/releases
环境部署:
解压后,dist 下就是各个版本的独立运行包,解压最新的 2.x 版本,双击打开 SpecRunner.html 即可运行测试,注意要在SpecRunner.html 的 <head> 内引用 Jasmine 的 js 文件和测试文件。想简单使用的话,将 soucre/spec 下的测试文件替换成你自己的即可。
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Jasmine Spec Runner v2.0.0</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.0/jasmine.css">
<script type="text/javascript" src="lib/jasmine-2.0.0/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.0/jasmine-html.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.0/boot.js"></script>
<!-- 被测试的代码 -->
<script type="text/javascript" src="src/xxx.js"></script>
</head>
<body>
</body>
</html>
快速入门
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="jquery-1.11.3.js"></script>
<link rel="stylesheet" type="text/css" href="jasmine-2.8.0/jasmine.css">
<script src="jasmine-2.8.0/jasmine.js"></script>
<script src="jasmine-2.8.0/jasmine-html.js"></script>
<script src="jasmine-2.8.0/boot.js"></script>
</head>
<body>
<script>
function get1() {
$.getJSON("http://localhost:8080/test/get").then(function(result) {
console.log(result);
});
}
// 每一个测试用例的超时时间
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
// 请求的接口的前缀 // http://localhost:8080/test
var base = "/ajaxserverapache";
//测试模块
describe("ajax", function() {
// 测试方法
it("get请求", function(done) {
// 服务器返回的结果
var result;
$.getJSON(base + "/get").then(function(jsonObj) {
result = jsonObj;
});
// 由于是异步请求,需要使用setTimeout来校验
setTimeout(function() {
expect(result).toEqual({
"data" : "get ok"
});
// 校验完成,通知jasmine框架
done();
}, 100);
});
});
</script>
</body>
</html>
核心
Suites(describe
)是Jasmine的核心,是一个测试集,里面包括多个specs(it
),而每个specs里面可能包含多个断言(expect
)。
1、Suites(describe
)
Suites使用describe()
来定义,其中传递两个参数为:
string
:用于描述测试组的名称function
:是测试组的主体函数
测试组的代码如下:
describe("This is a suite", function() {
it("This is a specs", function() {
var a = 'abc';
expect(a).toEqual('abc');
});
});
代码使用describe()
函数定义了一个名为“This is a suite”的测试组,测试组的内容在于判断a是否等于“abc”,主体内容将在后面继续介绍。
为了能够在测试开始前先进行部分初始化,或者在测试结束后对一些内容进行销毁,主体还包括四个全局函数:
- beforeEach():在describe函数中每个Spec执行之前执行。
- afterEach():在describe函数中每个Spec数执行之后执行。
- beforeAll():在describe函数中所有的Specs执行之前执行,但只执行一次,在Sepc之间并不会被执行。
- afterAll():在describe函数中所有的Specs执行之后执行,但只执行一次,在Sepc之间并不会被执行。
例如:
describe("This is a suite", function() {
beforeEach(function(){
var a = 'abc';
});
afterEach(function(){
a = '';
})
it("This is a specs1", function() {
expect(a).toEqual('abc');
});
});
上述代码中,在每一个specs执行前都会声明变量a为“abc”,而在测试之后,都会将其置为空字符串。
2、嵌套describe
Suites可以嵌套定义,每层都可以包含自己的specs,并且进行测试。同样可以包含各自的beforeEach()
和afterEach()
等函数。例如:
describe("This is the first describe", function() {
var a;
beforeEach(function() {
a = 'abc';
});
afterEach(function() {
a = '';
});
it("is just a function", function() {
expect(a).toEqual('abc');
});
describe("This is the second describe", function() {
var b;
beforeEach(function() {
b = 'abc';
});
it("is just a function too", function() {
expect(b).toEqual(a);
});
});
});
3、Specs(it
)
Specs是测试组里的每个测试体,其中用it()
函数定义测试体,传递两个参数: string
:用于描述测试体的名称 function
:测试体的主体内容
如上一段代码中:
it("This is a specs", function() {
expect(a).toEqual('abc');
});
则为一个测试体,当然,每个测试组Suites可以包含多个测试体,如下:
describe("This is a suite", function() {
it("This is a specs1", function() {
var a = 'abc';
expect(a).toEqual('abc');
});
it("This is a specs2", function() {
var b = {};
expect(b).toBe({});
});
it("This is a specs3", function() {
expect(c).toBeUndefined();
});
});
代码定义了一个测试组 “This is a suite”,其中包括三个 specs,分别测试 a 是否等于 ‘abc’,b 是否和空对象一致,c 是否为undefined。
4、断言(expect
)
Specs里面可能包含多个断言,只有在所有的断言都返回true时才会通过,否则测试失败。正如上面代码所示,expect(c).toBeUndefined();
为一个测试语句,判断c变量是否为undefined。
expect()传递一个参数,为actual,接收实际值,而后面紧跟着一个Matchers传递一个(或没有),来进行判断是否通过。如例子所示,c为实际值,而ToBeUndefined()则为Matchers,进行的比较则为c(实际值)是否为undefined。
再比如:expect(a).toEqual('abc');传递的实际值为a,与“abc”进行判断是否相等。
注意:it函数块可以包含多个expect过程,只要有其中的expect不符合期望,it就会测试不通过;而describe也可以包含多个it,只要有中有一个it报错,那么describe就会测试不通过。
5、Matchers
Jasmine定义了多个Matchers,用来测试一些变量是否通过。
常见的有:
- toBe():判断两个变量是否全等,类似于“===”;
- toNotBe():与上一个相反,判断两个变量是否不全等,类似于“!==”;
- toBeDefined():检查变量或属性是否已声明且赋值;
- toBeUndefined():与上一个相反;
- toBeNull():判断变量是否为null;
- toBeTruthy():判断变量如果转换为布尔值,是否为true;
- toBeFalsy():与上一个相反;
- toBeLessThan():与数值比较,是否小于;
- toBeGreaterThan():与数值比较,是否大于;
- toEqual():判断变量是否相等,相当于“==”;
- toContain():判断一个数组中是否包含元素(值)。只能用于数组,不能用于对象;
- toBeCloseTo():数值比较时定义精度,先四舍五入后再比较;
- toMatch():按正则表达式匹配;
- toNotMatch():与上一个相反;
- toThrow():检验一个函数是否会抛出一个错误。
- 注意:任何Matcher都能通过在expect调用Matcher前加上not来实现一个否定的断言(expect(a).not().toBe(false);)。
常见的Matchers使用代码如下:
describe("Included matchers:", function() {
it("The 'toBe' matcher compares with ===", function() {
//toBe()类似于"==="
var a = 12;
var b = a;
expect(a).toBe(b);//通过
expect(a).not.toBe(null);//通过,因为a!==null
});
describe("The 'toEqual' matcher", function() {
//toEqual()类似于"=="
it("works for simple literals and variables", function() {
var a = 12;
expect(a).toEqual(12);//通过
});
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() {
//toMatch()用于匹配正则表达式
var message = "foo bar baz";
expect(message).toMatch(/bar/);//通过,参数可以是正则表达式
expect(message).toMatch("bar");//通过,参数可以是字符串
expect(message).not.toMatch(/quux/);//通过,因为toMatch()匹配不到/quux/
});
it("The 'toBeDefined' matcher compares against `undefined`", function() {
//toBeDefined()判断参数是否定义
var a = {
foo: "foo"
};
expect(a.foo).toBeDefined();
expect(a.bar).not.toBeDefined();
});
it("The `toBeUndefined` matcher compares against `undefined`", function() {
//toBeUndefined()判断参数是否为undefined
var a = {
foo: "foo"
};
expect(a.foo).not.toBeUndefined();
expect(a.bar).toBeUndefined();
});
it("The 'toBeNull' matcher compares against null", function() {
//toBeNull()判断参数是否为空
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() {
//toBeTruthy()判断参数转化为布尔值时是否为true
var a, foo = "foo";
expect(foo).toBeTruthy();//通过,foo变量转变为true
expect(a).not.toBeTruthy();
});
it("The 'toBeFalsy' matcher is for boolean casting testing", function() {
//toBeFalsy()判断参数转化为布尔值时是否为false
var a, foo = "foo";
expect(a).toBeFalsy();
expect(foo).not.toBeFalsy();
});
it("The 'toContain' matcher is for finding an item in an Array", function() {
//toContain()判断元素是否存在于数组内。不适用于对象
var a = ["foo", "bar", "baz"];
expect(a).toContain("bar");
expect(a).not.toContain("quux");
});
it("The 'toBeLessThan' matcher is for mathematical comparisons", function() {
//toBeLessThan()判断实际值是否小于期望值
var pi = 3.1415926,
e = 2.78;
expect(e).toBeLessThan(pi);
expect(pi).not.toBeLessThan(e);
});
it("The 'toBeGreaterThan' matcher is for mathematical comparisons", function() {
//toBeGreaterThan()判断实际值是否大于期望值
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() {
//toBeCloseTo()数值比较时定义精度,先四舍五入后再比较
var pi = 3.1415926,
e = 2.78;
expect(pi).not.toBeCloseTo(e, 2);
expect(pi).toBeCloseTo(e, 0);
});
it("The 'toThrow' matcher is for testing if a function throws an exception", function() {
//toThrow()判断函数是否会抛出一个错误
var foo = function() {
return 1 + 2;
};
var bar = function() {
return a + 1;
};
expect(foo).not.toThrow();
expect(bar).toThrow();
});
it("The 'toThrowError' matcher is for testing a specific thrown exception", function() {
//toThrowError()判断函数是否抛出一个特别的错误,以下四种都能够通过测试
var foo = function() {
throw new TypeError("foo bar baz");
};
expect(foo).toThrowError("foo bar baz");
expect(foo).toThrowError(/bar/);
expect(foo).toThrowError(TypeError);
expect(foo).toThrowError(TypeError, "foo bar baz");
});
});
6、自定义Matchers
当然,用户可以自定义Matchers。在beforeEach()
或it()
函数里调用Jasmine.addMatchers()
,其中可以传递一个参数expected
作为测试值,而实际值则保存在this.actual
中,代码如下:
describe("This is a suite", function() {
beforeEach(function(){
var a = 'abc';
this.addMatchers({
toBeTrue : function(expected){
return this.actual==expected;
}
});
});
it("This is a specs1", function() {
expect(a).toBeTrue('abc');
});
});
代码在beforeEach()中调用this.addMatchers()定义了一个Matchers。定义了ToBeTrue(),传递一个参数expected与实际值作比较。
后面调用该Matchers时,代码expect(a).toBeTrue('abc');中,a则为实际值(this.actual),而“abc”则为参数expected。
该定义的Matchers与原有的toEqual()类似。
7、禁用Suites
通过调用xdescribe()
函数可以禁用Suites。代码执行时,这些Suites以及里面的任何spec都将跳过,因而他们的结果也不会出现在最终的输出结果中。例如:
xdescribe("A spec", function() {
var foo;
beforeEach(function() {
foo = 0;
foo += 1;
});
it("is just a function, so it can contain any code", function() {
expect(foo).toEqual(1);
});
});
输出结果如图:
8、挂起Specs
被挂起的spec不会执行。但是他们的名字仍会显示在pending的结果集中。
声明挂起的方式有三种:
- spec可以使用
xit()
来声明挂起; - spec声明时不添加任何函数体也可以在结果中达到挂起的效果;
- 如果你在spec体内的任何位置调用pending()函数,不管那个断言通过与否,该spec都将被挂起。当Suites结束后,pending所传递的字符串将被作为挂起的理由而显示。
例如:
describe("Pending specs", function() {
xit("can be declared 'xit'", function() {
//使用xit()函数挂起
expect(true).toBe(false);
});
it("can be declared with 'it' but without a function");
//使用空函数体将spec挂起
it("can be declared by calling 'pending' in the spec body", function() {
expect(true).toBe(false);
pending('this is why it is pending'); //调用pending()将其挂起
});
});
最终显示结果为:
9、this
关键字
除了在describe()
声明变量外,另外一个在beforeEach()
,it()
,afterEach()
之间共享变量的方法是使用this关键字。每一个spec的beforeEach()
,it()
,afterEach()
拥有相同的在下个spec的beforeEach()
,it()
,afterEach()
执行之前会延迟置空的空对象this
。
代码如下:
describe("A spec", function() {
beforeEach(function() {
this.foo = 0;
});
it("can use the `this` to share state", function() {
expect(this.foo).toEqual(0);
this.bar = "test pollution?";
});
it("prevents test pollution by having an empty `this` created for the next spec", function() {
expect(this.foo).toEqual(0);
expect(this.bar).toBe(undefined);
});
});
代码在每个spec之前声明this.foo
并赋值,在其it()
函数里能够共享该变量。而在it()
函数里声明的this.bar
,在下一个spec执行时无法获得,此时两者的this
关键字所指向的并不一致。
更多教程:Jasmine - 文档(二)