jasmine入门

武器档案

页面前端逻辑复杂度与日俱增,前端工程师写出来的javascript变得庞大甚至臃肿,维护的难度不断加大,你需要一个javascript单元测试框架,用于降低维护javascript代码时的出错风险,保证重构后的代码的兼容性,最重要的是减少人肉测试的过程,降低js代码维护成本。jasmine无疑是目前最优秀的javascript单元测试框架之一,目前淘宝UED正在使用,在易用性和质量上都非常不错。
单元测试的核心任务是建议一套针对javascript代码的实例库(如果你愿意可以覆盖你写的javascript所有的类的方法!)。

在jasmine的官网的第一行文字是“BDD for JavaScript”。

什么是BDD呢?

(PS:这部分内容很晦涩,不喜欢就直接跳过,不影响阅读。)
说真得明河也是一知半解,全称是“behavior-driven development”,即行为驱动开发。百度百科有句解释,“是一种敏捷软件开发技术,鼓励开发人员、质量保证和非技术或商业参与者都参与到项目中来。”BDD更像是一种团队的约定,javascript单元测试,也许对于你本人(开发该脚本的前端)意义不是特别突出,但对于整个团队,整个项目来说就是一种财富。

哪些是典型的BDD实践呢?
  • 为不同利益相关者建立共有的可实施的目标期望。(Establishing the goals of different stakeholders required for a vision to be implemented);
  • -_-!英语水平太差,不献丑了(Involving stakeholders in the implementation process through outside-in software development);
  • 使用例子去描述应用的行为或单元代码(Using examples to describe the behavior of the application, or of units of code);
  • 通过这些例子(单元测试)实现自动化的快速反馈和测试回归(Automating those examples to provide quick feedback and regression testing)。
1.学习jasmine的基础语法

(ps:如果你熟悉Rspec(一个BDD测试框架),那么jasmine对于你来说非常容易,因为jasmine风格上很接近Rspec。)
jasmine单元测试有二个核心的部分:describe 函数块和it函数块。接下来我们来看下这二个部分是如何工作的。

  1. //建立个describe块
  2. describe('JavaScript addition operator'function () { 
  3.     //建立it块
  4.     it('adds two numbers together'function () { 
  5.         //测试1+2是否等于3
  6.         expect(1 + 2).toEqual(3)
  7.     })
  8. });

describe和it函数都有二个参数:

  • 第一个参数:测试描述(一般使用英文,当然你使用中文也是完全没问题的);
  • 第二个参数:测试逻辑函数(具体干活的主)

在it的函数块(第二个参数)内,你可以针对你要测试的javascript代码书写相应的测试代码,上面的代码有行“expect(1 + 2).toEqual(3); ”,expect方法用于表明你测试的预期,toEqual是它的子方法,表示是否等于你的预期。所以这句代码可以翻译为,1 + 2等于3,如果等于3,那么it函数块测试通过,it测试通过,那么describe也就测试通过。
it函数块可以包含多个expect过程,只要有其中的expect不符合期望,it就会测试不通过;而describe也可以包含多个it,只要有中有一个it报错,那么describe就会测试不通过。
当然单纯的测试1+2是否等于3,完全没有意义,明河觉得单元测试的最主要作用在于对js类的接口进行的测试。比如明河写了个选择框组件:

  1. var Select = function(){
  2. };
  3. Select.prototype = {
  4.     show : function(){},
  5.     hide : function(){},
  6.     change : function(){},
  7.     width : function(){}
  8. }

那么我会针对这个类写下如下单元测试代码:

  1. describe('模拟选择框测试'function () { 
  2.     var select = new Select();
  3.     it('显示方法无误'function () { 
  4.          expect(select.show()).toEqual(true)
  5.     });
  6.     it('隐藏方法无误'function () { 
  7.          expect(select.hide()).toEqual(true)
  8.     });   
  9. });

show、hide方法执行正确时返回true,这里明河隐去代码具体实现。
当我下次重构select组件时,如果我漏写了hide方法或者把hide写成hidden,那么运行这个单元测试就会报错。当然也许你会说这个过程不是用肉眼就可以知晓吗?是的,明河不否认人地主观能动性的强大,但是思考个问题,如果你的类有很多方法,如果你需要知道方法各种调用的执行情况,那么人肉很容易出现纰漏。
接下来我们来看个实际的使用jasmine的项目例子。

2.如何在你的项目中应用jasmine?

如果你下载了jasmine的源文件,那么结构应该是如下图所示:

你可以运行下SpecRunner.html(实例页)体验下jasmine的测试界面,如下图:

当测试用例的背景全部为绿色时,表示测试通过,红色出现时表示测试失败,代码有问题。
SpecRunner.html是非常标准的单元测试,可以作为你项目测试页面模板。
接下来我们以实例包为例,里面已经包含了jasmine的源码了。实例包的目录如下:

打开实例包的SpecRunner.html。

在测试页面中引入jasmine库
  1. <link rel="stylesheet" type="text/css" href="spec/jasmine/jasmine.css">
  2.   <script type="text/javascript" src="spec/jasmine/jasmine.js"></script>
  3.   <script type="text/javascript" src="spec/jasmine/jasmine-html.js"></script>

必须引入这三个文件!

在测试页面中引入需要测试的代码文件
  1. <script type="text/javascript" src="src/convert.js"></script>
在测试页面中引入单元测试代码
  1. <script type="text/javascript" src="spec/convertSpec.js"></script>
初始化jasmine

通用的代码,copy到页面下即可。

  1. (function() {
  2.       var jasmineEnv = jasmine.getEnv();
  3.       jasmineEnv.updateInterval = 1000;
  4.  
  5.       var trivialReporter = new jasmine.TrivialReporter();
  6.  
  7.       jasmineEnv.addReporter(trivialReporter);
  8.  
  9.       jasmineEnv.specFilter = function(spec) {
  10.         return trivialReporter.specFilter(spec);
  11.       };
  12.  
  13.       var currentWindowOnload = window.onload;
  14.  
  15.       window.onload = function() {
  16.         if (currentWindowOnload) {
  17.           currentWindowOnload();
  18.         }
  19.         execJasmine();
  20.       };
  21.  
  22.       function execJasmine() {
  23.         jasmineEnv.execute();
  24.       }
  25.  
  26.     })();

接下来你就可以自由的在convertSpec.js中书写单元测试代码。

3.写测试用例?

可以把每个it块当做一个解释类的方法用法的例子,而describe就像一部对类进行解释的说明书,也就是说可以把测试代码当做“文档”来读。

建立describe
  1. describe( "Convert library"function () { 
  2.     describe( "distance converter"function () { 
  3.  
  4.         })
  5.  
  6.     describe( "volume converter"function () { 
  7.  
  8.     })
  9. });

从上面的代码可以看出describe是可以嵌套的,(一般不会出现嵌套太多的情况)。
这里的describe测试的是Convert library,即convert.js中的xConvert类(xConvert主要用于单位的转换)下的方法可用性。
需要测试二方面的内容:distance converter(距离单位转换),volume converter(体积单位转换),所以我们创建了二个子describe

对xConvert的API进行测试

先对距离单位转换进行测试:

  1. describe( "distance converter"function () { 
  2.     it("converts inches to centimeters"function () { 
  3.         expect(Convert(12"in").to("cm")).toEqual(30.48)
  4.     })
  5.  
  6.     it("converts centimeters to yards"function () { 
  7.         expect(Convert(2000"cm").to("yards")).toEqual(21.87)
  8.     })
  9. });

上面的代码提供了二个用例,将单位in转成cm,将cm转成yards,使用expect方法看结果是否符合预期,如果符合那么测试通过。
通过上面的测试代码,我们可以阅读到二个信息:

  • Convert函数有二个参数,第一个参数是数值型,为待转换的数字,第二个参数为单位;
  • Convert还有to字方法,用于转换成指定单位。

这就是明河所说的,单元测试可以但文档阅读的缘故。
接下来来看下体积转换的测试:

  1. describe( "volume converter"function () { 
  2.     it("converts litres to gallons"function () { 
  3.         expect(Convert(3"litres").to("gallons")).toEqual(0.79)
  4.     })
  5.  
  6.     it("converts gallons to cups"function () { 
  7.         expect(Convert(2"gallons").to("cups")).toEqual(32)
  8.     })
  9. });

代码跟距离转换非常类似就不再一一解释。
接下来我们再追加二个测试,用于测试当用户传入非法单位或不支持的单位时的情况。

  1. it("throws an error when passed an unknown from-unit"function () {
  2.         var testFn = function () {
  3.             Convert(1"dollar").to("yens");
  4.         };
  5.    
  6.         expect(testFn).toThrow(new Error("unrecognized from-unit"));
  7.     });
  8.     it("throws an error when passed an unknown to-unit"function () {
  9.         var testFn = function () {
  10.             Convert(1"cm").to("furlongs");
  11.         }
  12.         expect(testFn).toThrow(new Error("unrecognized to-unit"));
  13.     });

如果你运行SpecRunner.html,那么页面将会出现如下错误!

错误很明确的指向Convert变量未定义!!!好的,我们接下来打开src/convert.js,你就会发现变量错了!将xConvert改成Convert,你就会发现测试通过了!

当然这个错误其实是人为制造的错误,而且代码偏简单。代码越复杂,越有必要进行单元测试,才能保证你日后维护时,整个类逻辑的正确性。

第四步:学习匹配器

好了,通过以上的学习应该初步了解Jasmine的简单使用,下面将介绍两种匹配器:toEqualtoThrow。当然还有很多其他奇奇怪怪的匹配器,如果你有兴趣,你可以在wiki里面寻找它们。

toBeDefined / toBeUndefined

这里介绍的两种匹配器是用于测试一个变量是否被定义,当一个变量被定义了,你可以用toBeDefined去判断它,反之,你可以用toBeUndefined去判断一个变量是否未被定义。

  1. it("is defined"function () { 
  2.     var name = "Andrew"
  3.     expect(name).toBeDefined()
  4. }) 
  5.  
  6. it("is not defined"function () { 
  7.     var name
  8.     expect(name).toBeUndefined()
  9. });
toBeTruthy / toBeFalsy

如果我们需要对一个布尔变量进行验证测试,那么这两个匹配器是我们应该考虑使用的。

  1. it("is true"function () { 
  2.     expect(Lib.isAWeekDay()).toBeTruthy()
  3. })
  4. it("is false"function () { 
  5.     expect(Lib.finishedQuiz).toBeFalsy()
  6. });
toBeLessThan / toBeGreaterThan

如果我们需要对比数字的大小,可以运用这两个匹配器。

  1. it("is less than 10"function () { 
  2.     expect(5).toBeLessThan(10)
  3. })
  4. it("is greater than 10"function () { 
  5.     expect(20).toBeGreaterThan(10)
  6. });
toMatch

如果我们需要运用正则表达式进行相关验证,那么toMatch正好满足我们的需求。

  1. it("outputs the right text"function () { 
  2.     expect(cart.total()).toMatch(/\$\d*.\d\d/)
  3. });
toContain

这个匹配器非常有用,用来检查一个数组是否包含一个需要被验证的变量。

  1. it("should contain oranges"function () { 
  2.     expect(["apples""oranges""pears"]).toContain("oranges")
  3. });

还有很多的匹配器,这里就不一一介绍了,我们可以查看wiki来了解更多。但是你有没有想过,如果你想要的匹配器如果wiki里面没有怎么办?别担心,Jasmine允许我们建立自己的匹配器(第六步会提到),但是做这个之前,我们得学习一些其他的东西。(题外话:当然,我们需要通过jasmine建立一些匹配器,但是有些时候更重要的是,我们得知道代码的逻辑,这样方便我们建立可读性更强的测试代码。)

第五步:了解Before和After

(PS:下面的代码比较多,可以参照上一篇教程中的压缩包中的demo来看。)
当我们对代码进行测试的时候,很多变量需要初始化。但是这是一个痛苦的过程,因为在每段测试用例后,这个变量就会被新的测试代码重新赋值,所以jasmine提供beforeEach和afterEach方法让我们对变量进行重新申明。好了,首先让我们看看beforeEach是如何使用的:

  1. describe("MyObject"function () { 
  2.     var obj = new MyObject()
  3.  
  4.     beforeEach(function () { 
  5.         obj.setState("clean")
  6.     })
  7.  
  8.     it("changes state"function () { 
  9.         obj.setState("dirty")
  10.         expect(obj.getState()).toEqual("dirty")
  11.     }) 
  12.     it("adds states"function () { 
  13.         obj.addState("packaged")
  14.         expect(obj.getState()).toEqual(["clean""packaged"])
  15.     }) 
  16. });

在以上代码片段中,你可以看到,当一段测试用例运行前,obj的状态会被置为“clean”,如果我们不用beforeEach做这个初始化的话,那么obj的状态在下一次测试用例执行之前不会被重置,这样也就达不到测试的效果了,也就是说,beforeEach是作用于每个测试用例之前的。同样,jasmine也提供afterEach方法,该方法作用于每个测试用例执行之后。

  1. describe("MyObject"function () { 
  2.     var obj = new MyObject("clean")// sets initial state 
  3.  
  4.     afterEach(function () { 
  5.         obj.setState("clean")
  6.     })
  7.  
  8.     it("changes state"function () { 
  9.         obj.setState("dirty")
  10.         expect(obj.getState()).toEqual("dirty")
  11.     }) 
  12.     it("adds states"function () { 
  13.         obj.addState("packaged")
  14.         expect(obj.getState()).toEqual(["clean""packaged"])
  15.     }) 
  16. });

以上测试代码就是在每个测试用例执行完之后重置obj的状态为“clean”。

第六步:编写自定义匹配器

(PS:这一步其实可以跳过。)
回到前面第四步提及的场景,自定义的匹配器还是适合某些场景的。所以接下来我就来写一个自定义的匹配器,例如,我们可以增加一个匹配器在BeforeEach或者it函数里(当然,这里你会想把这个匹配器放在afterEach函数体里,但是这个应用场景并不广泛),如下所示:

  1. beforeEach(function () { 
  2.     this.addMatchers({ 
  3.  
  4.     })
  5. });

一个很简单的开始对吧?哈哈,好了,下面我们将在this.addMatchers这个函数体定义内容,这个内容关系到这个匹配器是如何运行的。举个例子,我们想定义一个匹配器做如下验证:一个数字是否在两个数字的之间。那么我们会这么写:

  1. beforeEach(function () { 
  2.     this.addMatchers({ 
  3.         toBeBetweenfunction (rangeFloorrangeCeiling) { 
  4.             if (rangeFloor > rangeCeiling) { 
  5.                 var temp = rangeFloor
  6.                 rangeFloor = rangeCeiling
  7.                 rangeCeiling = temp
  8.             } 
  9.             return this.actual > rangeFloor && this.actual < rangeCeiling
  10.         } 
  11.     })
  12. });

这里面toBeBetween传入两个参数,上限和下限,然后函数体返回布尔来确定这个值是否在传入的上限和下限之间,接下来我们看下面的测试用例是如何使用以上的方法的:

  1. it("is between 5 and 30"function () { 
  2.     expect(10).toBeBetween(530)
  3. })
  4.  
  5. it("is between 30 and 500"function () { 
  6.     expect(100).toBeBetween(50030)
  7. });

怎么样,很简单吧!这些就是SpecHelper.js这个文件做的,它还有很多其他的用处,有兴趣的话可以尝试阅读下它的源代码。

结束语

亲,jasmine这东西,真的很给力!上面的内容已经涵盖到jasmine的常用语法,其实还少了异步测试一块内容,这块内容后面由明河同学补充。
jasmine的语法其实很简单,熟能生巧,希望这篇文章能起到抛砖引玉的作用。

附录

其他预定义匹配器

expect(x).toEqual(y); compares objects or primitives x and y and passes if they are equivalent

expect(x).toBe(y); compares objects or primitives x and y and passes if they are the same object

expect(x).toMatch(pattern); compares x to string or regular expression pattern and passes if they match

expect(x).toBeDefined(); passes if x is not undefined

expect(x).toBeUndefined(); passes if x is undefined

expect(x).toBeNull(); passes if x is null

expect(x).toBeTruthy(); passes if x evaluates to true

expect(x).toBeFalsy(); passes if x evaluates to false

expect(x).toContain(y); passes if array or string x contains y

expect(x).toBeLessThan(y); passes if x is less than y

expect(x).toBeGreaterThan(y); passes if x is greater than y

expect(function(){fn();}).toThrow(e); passes if function fn throws exception e when executed



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值