前言
测试驱动开发,对软件质量起到了规范性的控制。未写实现,先写测试,一度成为Java领域研发的圣经。随着Javascript兴起,功能越来越多,代码量越来越大,开发人员素质相差悬殊,真的有必要建立对代码的规范性控制。jasmine就是为团队合作而生。
Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, Node.js projects, or anywhere that JavaScript can run.
--引用jasmine自己的介绍
jasmine是一个BDD行为驱动开发测试框架,它不依赖于浏览器,DOM,或任何JavaScript框架,它使用各种网站当然包括nodejs,当然后面我要说的也是基于nodejs。
jasmine中使用到的类库,我都是通过bower一键搞定。
<link rel="shortcut icon" type="image/png" href="jasmine/lib/jasmine-2.0.0/jasmine_favicon.png"> <link rel="stylesheet" type="text/css" href="jasmine/lib/jasmine-2.0.0/jasmine.css"> <script type="text/javascript" src="jasmine/lib/jasmine-2.0.0/jasmine.js"></script> <script type="text/javascript" src="jasmine/lib/jasmine-2.0.0/jasmine-html.js"></script> <script type="text/javascript" src="jasmine/lib/jasmine-2.0.0/boot.js"></script>
jasmine 开发主要依赖四个部分:
既然上面都说到了运行环境是浏览器,当然我们的第一步是创建一个页面
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>jasmine test</title> 6 <link rel="stylesheet" href="bower_components/jasmine/lib/jasmine-core/jasmine.css"> 7 <script src="bower_components/jasmine/lib/jasmine-core/jasmine.js"></script> 8 <script src="bower_components/jasmine/lib/jasmine-core/jasmine-html.js"></script> 9 </head> 10 <body> 11 <h1>jasmine test</h1> 12 <!--启动脚本,基本写法是固定。网上很多都将这个脚本放在测试案例之后,至少在我们测试的版本是不可以的,之前的版本我并没有测试,那样是否正确--> 13 <script src="spec/bootstrap.js"></script> 14 <!--测试目标文件--> 15 <script src="app/app.js"></script> 16 <!--测试案例--> 17 <script src="spec/appSpec.js"></script> 18 </body> 19 </html>
启动脚本bootstrap.js,是jasmine为启动脚本,基本上都固定写法。2.0以后也可以直接引用jasmine提供boot.js
1 ;(function() { 2 window.jasmine = jasmineRequire.core(jasmineRequire); 3 jasmineRequire.html(jasmine); 4 var env = jasmine.getEnv(); 5 var jasmineInterface = jasmineRequire.interface(jasmine, env); 6 var jasmineEnv = jasmine.getEnv(); 7 if (typeof window == "undefined" && typeof exports == "object") { 8 9 extend(exports, jasmineInterface); 10 11 } else { 12 extend(window, jasmineInterface); 13 } 14 var queryString = new jasmine.QueryString({ 15 getWindowLocation: function() { 16 return window.location; 17 } 18 }); 19 20 var catchingExceptions = queryString.getParam("catch"); 21 22 env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); 23 var htmlReporter = new jasmine.HtmlReporter({ 24 env: env, 25 onRaiseExceptionsClick: function() { 26 queryString.setParam("catch", !env.catchingExceptions()); 27 }, 28 getContainer: function() { 29 return document.body; 30 }, 31 createElement: function() { 32 return document.createElement.apply(document, arguments); 33 }, 34 createTextNode: function() { 35 return document.createTextNode.apply(document, arguments); 36 }, 37 timer: new jasmine.Timer() 38 }); 39 40 41 env.addReporter(jasmineInterface.jsApiReporter); 42 env.addReporter(htmlReporter); 43 44 var specFilter = new jasmine.HtmlSpecFilter({ 45 filterString: function() { 46 return queryString.getParam("spec"); 47 } 48 }); 49 50 env.specFilter = function(spec) { 51 return specFilter.matches(spec.getFullName()); 52 }; 53 window.setTimeout = window.setTimeout; 54 window.setInterval = window.setInterval; 55 window.clearTimeout = window.clearTimeout; 56 window.clearInterval = window.clearInterval; 57 var currentWindowOnload = window.onload; 58 59 window.onload = function() { 60 if (currentWindowOnload) { 61 currentWindowOnload(); 62 } 63 htmlReporter.initialize(); 64 env.execute(); 65 }; 66 67 function extend(destination, source) { 68 for (var property in source) destination[property] = source[property]; 69 return destination; 70 } 71 72 }());
我们直接就只写测试案例测试我们的代码是否可以正常使用,直接给appSpec.js
1 describe("测试toBe的作用", function() { 2 it("toBe相当于===", function() { 3 var a = 12, 4 b = a; 5 expect(a).toBe(b); 6 expect(a).not.toBe(null); 7 expect(false == 0).toBe(true); 8 }); 9 })
jasmine
执行正常,跟预期的一样执行成功。
你现在是不是在想刚才还说基于浏览器,结果还是在命令行中执行了!!!
那我们直接打开浏览器,看执行结果:
跟命令行显示的一样都是成功的。有的小伙伴是不是有开始想迫不及待的尝试一下错误的情况了。好吧,我们改动一下,appSpec.js 看一下结果。
describe("测试toBe的作用", function() { it("toBe相当于===", function() { var a = 12, b = a; expect(a).not.toBe(b); expect(a).not.toBe(null); expect(false == 0).toBe(true); }); })
再刷新一下页面
弱弱的吐槽一下:相对qunit的界面这个真心丑。不过比之前版本的好看多了。
上一张让大家欣赏比较一下:
qnuit
之前jasmine截图:
跑远了,现在还是回到我们jasmine的学习吧。
1、it
一个测试案例,也就是这里的一个spec,一个it中可以包含多个判断断言。
it("I am it", function() { var a = 12, b = a; expect(a).toBe(b); expect(a).not.toBe(null); expect(false == 0).toBe(true); });
这就是简单一个it,第一个是一个字符串,简单描述测试内容,后面的为正式的测试内容。
2、describe
相当测试分组,里面包含n个it。decribe是可以嵌套
describe("I am describe", function() { it("I am it", function() { var a = 12, b = a; expect(a).toBe(b); expect(a).not.toBe(null); expect(false == 0).toBe(true); });
//当然我是可以嵌套的 describe("I am the second describe", function() { it("I am it", function() { var a = 12, b = a; expect(a).toBe(b); expect(a).not.toBe(null); expect(false == 0).toBe(true); }); }) })
用describe来描述一组测试案例的功能。
3、beforeEach和afterEach
都是定义在一个describe中,beforeEach在所有it执行前执行,afterEach刚好相反在it执行之后执行。
这里两个方法很简单,就是是用来做初始化和结束做相关操作的。
我们上一段代码来猜猜它的执行顺序
afterEach(function(){ console.log("afterEach") }) it("I am it", function() { console.log("it") }); beforeEach(function(){ console.log("beforeEach") })
大家来猜猜执行的顺序是什么?
聪明的同学肯定猜到了执行的结果:beforeEach》it》afterEach,没错这个执行跟代码的输入无关,及时将after和before的顺序对调执行的结果都是先after然后it最后才是before。
我们将这个代码重新加一段,大家再来猜猜执行顺序:
afterEach(function(){ console.log("afterEach") }); it("I am it", function() { console.log("it1") }); beforeEach(function(){ console.log("beforeEach") }); it("I am it", function() { console.log("it2") });
聪明的同学是不是开始猜了?心里默想的是不是?beforeEach》it1》it2》afterEach
我们直接看chrome上的执行结果吧。
why?为什么不是我们想的那样?
其实我们通过上一个执行结果就能猜到其实describe中的函数并不是这样直接就执行的。我写了一个方法来模拟我们刚才看到执行结果
;(function(){ //这里用来定义一个describe中的before,after和it定义的函数 var _beforeEachs, _afterEachs, _its=[]; function describe(desc,fn){ console.log(desc); fn && fn(); for(var i=0,len=_its.length;i<len;i++){ if(_its[i]){ //这里就是为什么jasmine执行结果为刚才那样的原因,每一次都将before和after执行一次。 //顺便也解释了不管你before和after,在定义的顺序如果执行的顺序都是固定的 _beforeEachs && _beforeEachs(); _its[i] && _its[i](); _afterEachs && _afterEachs(); } } } function beforeEach(fn){ _beforeEachs = fn; } function afterEach(fn){ _afterEachs = fn; } function it(desc,fn){ _its.push(fn) } //将我们之前的测试案例重新写一次 describe("",function(){ afterEach(function(){ console.log("afterEach") }); it("I am it", function() { console.log("it1") }); beforeEach(function(){ console.log("beforeEach") }); it("I am it", function() { console.log("it2") }); }) })();
我这里一部分代码只是简单的还原了一下实现原理,当然jasmine处理的比我写的更加复杂,有兴趣的同学可以研究一下它的源码。
4、expect
细心的同学也注意了我刚才代码在chrome中报错了Spec *** has no expectations,其实只是我们的例子中没有expect,其他那些只是对测试的描述真正的断言是通过expect来处理的。
describe("test jasmine API", function() { //toBe 相当于 === 值和类型相等 //但是只能比较字面量等基本类型 it("toBe is ===",function(){ var a =2, b =a; expect(a).toBe(b); expect(a).not.toBe("2"); }) //toEqual 包含了toBe比较以外还能比较对象和数组 it("toEqual 处理对象比较",function(){ var a = 12, b = a; expect(a).toEqual(b); a = {name:'river'}; b = {name:'river'}; expect(a).not.toBe(b) expect({}).not.toBe({}) expect([]).not.toBe([]) //这些都是会报错的 //expect({}).toBe({}) //expect([]).toBe([]) expect(a).toEqual(b) expect({}).toEqual({}) expect([1]).toEqual([1]) }) //toMatch 通过正则表达式去比较 it("toMatch 正则匹配方式",function(){ var contents = "absdacvad"; expect(contents).toMatch(/a/); expect(contents).not.toMatch(/z/); expect(contents).toMatch(/d$/); }) //toBeDefined 判断是否声明且赋值 it("toBeDefined 判断是否声明且赋值",function(){ var a, b = 1, c = { name:'abc' }; //expect(a).toBeDefined();//声明未赋值 @错 //expect(d).toBeDefined();//未声明错 expect(a).not.toBeDefined(); expect(b).toBeDefined(); expect(c).toBeDefined(); expect(c.name).toBeDefined(); }) //toBeUndefined 判断是否未赋值,这里和toBeDefined相反, //唯一的区别就是只是判断是否赋值,如果没有声明 //通过 expect(z).toBeUndefined() 会出错 // it("toBeUndefined 判断是否未赋值",function(){ var a, b=2,c; expect(a).toBeUndefined(); expect(b).not.toBeUndefined(); expect(c).toBeUndefined(); }) //toBeNull 是否null it("toBeNull 是否null",function(){ var a = null; expect(a).toBeNull(); expect(null).toBeNull(); }) //toBeTruthy 如果转换为布尔值,是否为true //值得提一下,比较原理:其实就是将值强制转换成boolean // !!a 通过!!转换完之后进行比较下面的toBeFalsy 也一样 it("toBeTruthy 如果转换为布尔值,是否为true",function(){ expect(1).toBeTruthy(); expect(0).not.toBeTruthy(); expect([]).toBeTruthy(); expect([] == false).toBeTruthy(); expect({}).toBeTruthy(); }) //toBeFalsy 如果转换为布尔值,是否为false it("toBeFalsy 如果转换为布尔值,是否为false",function(){ expect(0).toBeFalsy(); expect(!1).toBeFalsy(); expect(!{}).toBeFalsy(); }) //toContain 数组中是否包含元素(值)。只能用于数组,不能用于对象 it("toContain 数组中是否包含元素(值)。只能用于数组,不能用于对象",function(){ var arrays = [1,2,3,4,5]; expect(arrays).toContain(5); expect(arrays).not.toContain(6); //不能用于对象 expect("name").not.toContain({name:"name"}); }) //数值比较 //toBeLessThan 小于 //toBeGreaterThan 大于 it("toBeLessThan || toBeGreaterThan",function(){ expect(5).toBeLessThan(6); expect(6).toBeGreaterThan(5); //只是判断大于或小于 不存在等于 也就是只有 > || < 不存在 >= || <= //expect(5).toBeGreaterThan(5); }) //toBeCloseTo 数值比较时定义精度,然后四舍五入之后进行比较。 //第二个参数为精度 默认保留2位 it("toBeCloseTo 数值比较时定义精度,先四舍五入后再比较",function(){ var pi = 3.14, f = 2.99, d = 3.45, c = 3.49, z = 3.41; expect(pi).toBeCloseTo(f, 0); expect(pi).toBeCloseTo(c, 0); expect(d).toBeCloseTo(c, 1); expect(c).not.toBeCloseTo(z, 1); }) //toThrow 判断是否会抛出异常 //值得注意的是这里必须是函数 it("toThrow 判断是否会抛出异常",function(){ var fn = function(){ },errFn = function(){ throw new Error("this is Error");//手动抛出一个错误 }; expect(fn).not.toThrow(); expect(errFn).toThrow(); expect(function(){ a++;//产生一个语法错误 }).toThrow(); }) })
案例是多了点,但是这些都是以后测试案例中会用到的。
这是整个案例的执行结果,都是成功。当然你可以将案例中注释的案例放开看一下每个API的功能。测试各种场景。
好啦,Jasmine的第一部分就讲完了。
整个例子中用到的代码也送上让大家参考:百度网盘地址:http://pan.baidu.com/s/1i3KgSst
参考链接: