今天的内容关键字为测试单元测试


1. 什么是单元测试

在计算机编程中,单元测试(又称为模块测试)是针对程序模块(件软计设的小最位单)来停止确正性验检的测试作工。程序单元是用应的小最可测试部件。在进程化编程中,一个单元就是单个程序、数函、进程等;对于面向象对编程,小最单元就是方法,括包类基(超类)、抽象类、或者派生类(类子)中的方法。

每一个想理的测试案例独立于其它案例;为测试时断绝模块,经常用应stubs、mock 或fake等测试马甲程序。单元测试常通由件软发开人员编写,于用保确他们所写的代码符合件软需和求遵守发开标目。

单元测试的标目是断绝程序模块并证明这些单个模块是确正的。单元测试能保确在发开进程的初期能就发现问题,是为了让程序“死得更早”。我们该应从发开的初期就为有所数函和方法编写单元测试,可读性强的单元测试可以使程序员便方地查检代码片断是不是然依常正作工。精良计设的单元测试案例覆盖程序单元分支和环循条件的有所路径。用采种这自底向上的测试路径,先测试程序模块再测试模块的集合,一旦变革致使错误产生,借助于单元测试可以速快定位并修复错误。


2. JavaScript单元测试现状

单元测试在后台发开中非常风行和及普,比如JAVA发开者的JUnit等,而在前端发开中则用应的非常少。究其原因,要主是单元测试更适于用逻辑代码的测试,这对于JAVA等后台编程语言来讲测试起来非常便方,但是前端发开很多时候要要UI打交道,UI相干的代码不是不可以停止单元测试,但的确很费事,比起逻辑代码来讲难题多了,这就致使了单元测试在前端发开没有及普起来。

但是随着单元测试的及普,尤其是迅速发开的动推,出现了很多优良的JavaScript单元测试框架,如QUnit、Jasmine等。有所的这些框架基本上都能对Javascript代码停止很好的测试,当然UI部份的代码测试一样比拟费事,但是我们可以通过心精结构我们的测试代码来测试部份UI代码。但是每一个框架都不是全能的,它们都有各自善于的范畴,上面选取了几个有具代表性的框架停止绍介。


3. 单元测试经常使用框架


l QUnit框架


a) 简介

QUnit是jQuery团队发开的JavaScript单元测试工具,能功强大且用应单简。前目有所的JQuery代码都用应QUnit停止测试,原生的JavaScript也可以用应QUnit。

最初,John Resig将QUnit计设为jQuery的一部份。2008年,QUnit才有了自己的名字、主页和API文档,也开始答应其他人用它来做单元测试。但时当QUnit还是基于jQuery的。直到2009年,QUnit才可以全完的独立运行。


b) 点优

用应起来非常便方,有英俊的观外和整完的测试能功(括包异步测试);

非常单简,轻易上手,前目公然的API只有19个;

不要需赖依其它任何件软包或框架,只要能运行JS的方地能就够,QUnit本身只有一个JS件文和CSS件文,当然如果要需可以和jQuery等其它框架集成;

不仅支撑在浏览器中测试,还支撑在Rhino和node.js等后端测试。


c) 足不

对主动化支撑好不,很难和Ant、Maven或主动构建等工具集成,要主用在浏览器中停止测试。


d) API

QUnit有所的API可以分为类三:Setup,Assertions,Asynchronous Testing,上面就分别对这些API做些绍介:

Setup:

test( name, [expected], testFun ) 代表QUnit中的一个测试

name:要测试的称名,比如“加法数函”或“add”等

expected:可选参数,用来表现该测试数函的言断的量数,是个正整数

testFun:一个数函,有所的测试代码都该应括包在该数函里,常通这是一个匿名数函。

例:



​test(“add ​​​​function​​​​”, 1, ​​​​function​​​​() {​​​​​​​​equal(add(1, 2), 3);​​​​});​


asyncTest( name, [expected], testFun ) 代表QUnit中的一个异步测试,参数同test

expect( amount ) 用在测试数函中,于用声明测试言断的量数,这个数函和test中的expected参数的作用是一样的。要主作用就是查检你声明的个数和你写的言断的现实个数是不是致一。

module( name, [lifecycle] ) 要主于用测试数函的分组,一个module数函为一个分组,比如module(“validate”)表现面后的测试用例都是validate相干的代码,或者module(“common.js”),标明面后的测试用例都是common.js里头的代码。一个测试件文可以写多个module。

name:分组或者模块的称名

lifecycle:可选参数,它是一个象对,可以设置setup和teardown回调数函

例:



​module(“common.js”, ​​​​​​​​{​​​​​​​​setup:​​​​function​​​​(){},​​​​​​​​teardown: ​​​​function​​​​() {} ​​​​​​​​}​​​​);​


setup:在module开始之前执行,可认为该module上面的测试代码做一些准备作工

teardown:将会在该module的有所测试代码执行后执行,比如做一些理清还原作工等。

QUnit.init( ) 于用初始化QUnit测试框架,常通这个数函是不要需我们手工调用的。

QUnit.reset( ) 重设数函,常通是在每一个test数函执行后由QUnit自己调用来重设个整QUnit测试境环,当然必要时我们自己也可以调用它来原复,不经常使用。

Assertions:

ok( state, [message] ) 言断。state值为true时表现通过,否则失败。

equal( actual, expected, [message] ) 比拟参数actual和expected是不是等相,相当于 ==

notEqual( actual, expected, [message] ) 比拟两个参数是不是不等相,相当于 !=

deepEqual( actual, expected, [message] ) 要主于用数组和象对等型类的值是不是等相,会递归遍历它们所含包的值是不是等相。

notDeepEqual( actual, expected, [message] ) 要主于用数组和象对等型类的值是不是不等相,会递归遍历它们所含包的值是不是不等相。

strictEqual( actual, expected, [message] ) 比拟两个参数是不是格严等相,相当于 ===

notStrictEqual( actual, expected, [message] ) 比拟两个参数是不是不格严等相,相当于 !==

throws( block, expected, [message] ) 测试block数函是不是抛出一个异常,抛出则通过,不抛则失败。

block:我们要测试的数函

expected:可选参数,是一个型类,用来验证第一个数函抛出的异常是不是是我们预期的型类。

例:



​function​​​​CustomError( message ) {​​​​​​​​this​​​​.message = message;​​​​}​​​​CustomError.prototype.toString = ​​​​function​​​​() {​​​​​​​​return​​​​this​​​​.message;​​​​};​​​​throws(​​​​​​​​function​​​​() {​​​​​​​​throw​​​​new​​​​CustomError(“some error description”);​​​​​​​​},​​​​​​​​CustomError,​​​​​​​​"raised error is an instance of CustomError"​​​​);​


Asynchronous Testing:

stop( [increment] ) 停止测试的运行,于用异步测试。在异步测试时一般先把QUnit的test runner停下来。

increment:增长停止的间时。

start( [decrement] ) 当异步调用胜利后就该应把停止的test runner动启起来让它接着往前跑

decrement:用来增长停止的间时。

例:



​test( ​​​​"a test"​​​​, ​​​​function​​​​() {​​​​​​​​stop();​​​​​​​​var​​​​result = ​​​​null​​​​;​​​​​​​​$.ajax(​​​​​​​​url,​​​​​​​​{},​​​​​​​​function​​​​(data){​​​​​​​​result = data;​​​​​​​​}​​​​​​​​);​​​​​​​​setTimeout(​​​​function​​​​() {​​​​​​​​equals(result, ​​​​"success"​​​​);​​​​​​​​start();​​​​​​​​}, 150 );​​​​});​



e) 用应

test.html

导入qunit.css,qunit.js

顺次导入被测试件文src.js和测试件文test.js

测试单元测试javascript单元测试及框架介绍_sed

src.js里是我们要测试的一些数函

测试单元测试javascript单元测试及框架介绍_html_02

test.js里放我们的测试

测试单元测试javascript单元测试及框架介绍_sed_03

打开test.html,显示:

测试单元测试javascript单元测试及框架介绍_html_04

如果期望值与数函执行的结果不致一,会报错:

test.js

测试单元测试javascript单元测试及框架介绍_sed_05

test.html显示:

测试单元测试javascript单元测试及框架介绍_javascript_06

期望值与结果不符,测试不通过。

与浏览器主动化测试工具集成的接口:

都是QUnit主动调用的一些数函,一般不用改,也可以自己定制

QUnit.log(Function({ result, actual, expected, message })) 这个接口会在每一个言断执行后被主动调用

result:言断是不是通过

message:言断里的message参数

例:



​QUnit.log(​​​​function​​​​(details){​​​​​​​​alert(“Log: ” + details.result + “ ” + details.message);​​​​})​


QUnit.testStart(Function({ name })) 在每一个测试数函执行前被主动调用

name:测试数函中的name参数值

QUnit.testDone(Function({ name, failed, passed, total })) 在每一个测试数函结束后执行被主动调用

name:同上

failed:指失败言断的个数

passed:指胜利言断的个数

total:有所言断的个数

QUnit.moduleStart(Function({ name })) 在每一个module有所的测试代码执行前被主动调用

name:module数函中name参数的值

QUnit.moduleDone(Function({ name, failed, passed, total })) 在每一个module有所的测试代码执行完之后被主动执行

failed:指失败言断的个数

passed:指胜利言断的个数

total:指有所言断的个数

QUnit.begin(Function()) 在有所的测试代码调用之前运行

QUnit.done(Function({ failed, passed, total, runtime })) 在有所的测试代码调用之后运行

failed:指失败言断的个数

passed:指胜利言断的个数

total:指有所言断的个数

runtime:有所代码的执行间时


l Jasmine框架


a) 简介

Jasmine是一个有名的JavaScript单元测试框架,它是独立的行为驱动发开框架,语法清晰易懂。

行为驱动发开(BDD):是一种迅速件软发开的技术,它鼓励件软项目中的发开者、QA和非技术人员或商业参与者之间的协作。BDD最初是由Dan North在2003年命名,它括包验收和客户测试驱动等的极限编程的实践,作为对测试驱动发开的回应。在过去的数年里,得到了极大的发展。

BDD的重点是通过与利益相干者的讨论取得对预期的件软行为的清醒认识。它通过用自然语言书写非程序员可读的测试用例扩展了测试驱动发开方法。行为驱动发开人员用应混合了范畴中统一的语言的母语语言来描述他们的代码的目的。这让发开者得以把精力集中在代码该应怎么写,而不是技术细节上,而且也最大程度的增长了将代码编写者的技术语言与商业客户、用户、利益相干者、项目管理者等的范畴语言之间来回翻译的代价。

BDD的做法括包:

l 确立不同利益相干者要实现的远景标目

l 用应特性注入方法绘制出达到这些标目所要需的特性

l 通过由外及内的件软发开方法,把涉及到的利益相干者融入到实现的进程中

l 用应例子来描述用应程序的行为或代码的每一个单元

l 通过主动运行这些例子,提供速快反馈,停止回归测试

l 用应“应当(should)”来描述件软的行为,以帮助阐明代码的职责,以及回答对该件软的能功性的质疑

l 用应“保确(ensure)”来描述件软的职责,以把代码本身的效用与其他单元(element)代码带来的边际效用中区分出来。

l 用应mock作为还未编写的相干代码模块的替身

BDD特性注入:一个公司可能有多个会带来商业利益的不同愿景,常通括包盈利、省钱或保护钱。一旦某个愿景被发开小组确定为当前条件下的最佳愿景,他们将要需更多的帮助来胜利实现这个远景。

然后确定该愿景的要主利益相干者,会带入其他的利益相干者。每一个相干者要定义为了实现该愿景他们要需完成的标目。例如,法务部门可能要求某些监管要得到满足。市场营销负责人可能要参加将用该应件软的用户的社区。安全专家要需保确该件软不会受到SQL注入的攻击。

通过这些标目,会定义出要实现这些标目所要需的大概的题目或者特性集合。例如,“答用应户排序贡献值”或“交易审计”。从这些主题,可以确定用户能功以及用户界面的第一批细节。


b) 点优

它是基于行为驱动发开实现的测试框架,它的语法非常贴近自然语言,单简明了,轻易理解。

能很便方的和Ant、Maven等停止集成停止主动化测试,也可以便方和Jekins等持续集成工具停止集成,可以生成测试结果的XMl文档。

它有丰富的API,同时用户也支撑用户扩展它的API,这一点很少有其它框架能够做到。

用应便方单简,只要需引入两个js件文即可

不仅支撑在浏览器中测试,还支撑在Rhino和node.js等后端测试。

对于Ruby语言有特别的支撑,能够非常便方的集成到Ruby项目中去


c) 足不

在浏览器中的测试界面不如QUnit美观、详细。


d) API

it(string, function) 一个测试Spec

string:测试称名

function:测试数函

describe (string, function) 一个测试组开始于全局数函describe,一个describe是一个it的集合。describe含包n个it,一个it含包n个判断言断 Suite

string:测试组称名

function:测试组数函



​describe(​​​​"测试add()数函"​​​​, ​​​​function​​​​() {​​​​​​​​it(​​​​"1 + 1 = 2"​​​​, ​​​​function​​​​(){​​​​​​​​expect(add(1, 1)).toBe(2);​​​​​​​​});​​​​});​


beforeEach(function) 定义在一个describe的有所it执行前做的操作

afterEach(function) 定义在一个describe的有所it执行后做的操作

expect(a).matchFunction(b)

expect(a).not.matchFunction(b) 期望a和b满足匹配方式matchFunction

matchFunctions:

toBe 相当于===,处理单简字面值和变量



​it(​​​​"toBe相当于==="​​​​, ​​​​function​​​​(){​​​​​​​​var​​​​a = 12;​​​​​​​​var​​​​b = a;​​​​​​​​expect(a).toBe(b);​​​​​​​​expect(a).not.toBe(​​​​null​​​​);​​​​​​​​expect(​​​​false​​​​== 0).toBe(​​​​true​​​​);​​​​});​​​​it(​​​​"toBe不能当==用"​​​​, ​​​​function​​​​(){​​​​​​​​expect(​​​​false​​​​).toBe(0);​​​​});​


测试单元测试javascript单元测试及框架介绍_java_07

toEqual 处理单简字面值和变量,而且可以处理象对,数组



​it(​​​​"toEqual可以处理字面值,变量和象对"​​​​, ​​​​function​​​​(){​​​​​​​​var​​​​a = 12;​​​​​​​​expect(a).toEqual(12);​​​​​​​​var​​​​foo = {key : ​​​​"key"​​​​};​​​​​​​​var​​​​bar = {key : ​​​​"key"​​​​};​​​​​​​​expect(foo).toEqual(bar);​​​​​​​​var​​​​arr1 = [];​​​​​​​​arr1[​​​​"p1"​​​​] = ​​​​"string1"​​​​;​​​​​​​​var​​​​arr2 = [];​​​​​​​​arr2[​​​​"p1"​​​​] = ​​​​"string1"​​​​;​​​​​​​​var​​​​obj = {};​​​​​​​​obj[​​​​"p1"​​​​] = ​​​​"string1"​​​​;​​​​​​​​expect(arr1).toEqual(arr2);​​​​​​​​expect(arr1).toEqual(obj);​​​​});​


toMatch 按正则式检索。



​it(​​​​"toMatch匹配正则式"​​​​, ​​​​function​​​​(){​​​​​​​​var​​​​message = ​​​​"foo bar baz"​​​​;​​​​​​​​expect(message).toMatch(/bar/);​​​​​​​​expect(message).toMatch(​​​​"bar"​​​​);​​​​​​​​expect(message).not.toMatch(/quux/);​​​​​​​​expect(message).toMatch(/^f/);​​​​​​​​expect(message).not.toMatch(/f$/);​​​​});​


toBeDefined 是不是已声明且赋值



​it(​​​​"toBeDefined检测变量非undefined"​​​​, ​​​​function​​​​(){​​​​​​​​var​​​​a = { key : ​​​​"key"​​​​};​​​​​​​​expect(a.key).toBeDefined();​​​​​​​​expect(a.foo).not.toBeDefined();​​​​​​​​//expect(c).not.toBeDefined(); //未声明出错​​​​​​​​var​​​​b;​​​​​​​​expect(b).not.toBeDefined();​​​​});​


象对.未声明属性.not.toBeDefined(); 通过

未声明变量.not.toBeDefined(); 报错

toBeUndefined 是不是undefined

toBeNull 是不是null

toBeTruthy 如果转换为布尔值,是不是为true

toBeFalsy 如果转换为布尔值,是不是为false

toContain 数组中是不是含包元素(值)。只能于用数组,不能于用象对



​it(​​​​"toContain验检数组中是不是含包元素(值)"​​​​, ​​​​function​​​​(){​​​​​​​​var​​​​a = [​​​​"foo"​​​​, ​​​​"bar"​​​​, ​​​​"baz"​​​​];​​​​​​​​expect(a).toContain(​​​​"bar"​​​​);​​​​});​


toBeLessThan 数值比拟,小于

toBeGreaterThan 数值比拟,大于

toBeCloseTo 数值比拟时定义精度,先四舍五入后再比拟



​it(​​​​"toBeCloseTo数值比拟,指定精度,先四舍五入再比拟"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​pi = 3.1415926, e = 2.78;​​​​​​​​expect(pi).toBeCloseTo(e, 0);​​​​​​​​expect(pi).not.toBeCloseTo(e, 0.1);​​​​});​


toThrow 验检一个数函是不是会抛出一个错误



​it(​​​​"toThrow验检一个数函是不是会抛出一个错误"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​foo = ​​​​function​​​​() {​​​​​​​​return​​​​1 + 2;​​​​​​​​};​​​​​​​​var​​​​bar = ​​​​function​​​​() {​​​​​​​​return​​​​a + 1;​​​​​​​​};​​​​​​​​expect(foo).not.toThrow();​​​​​​​​expect(bar).toThrow();​​​​});​


注:describe可嵌套

xdescribe 和 xit:路过不执行,结果不显示。像display:none。点控制栏中skipped显示


Spy 存储数函的被调用情况和参数(数函监视器,记录被调用情况,但数函并不真执行)



​describe(​​​​"对spy数函的测试"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​foo, bar = ​​​​null​​​​;​​​​​​​​beforeEach(​​​​function​​​​() {​​​​​​​​foo = {​​​​​​​​setBar: ​​​​function​​​​(value) {​​​​​​​​bar = value;​​​​​​​​}​​​​​​​​};​​​​​​​​spyOn(foo, ​​​​'setBar'​​​​); ​​​​//foo为spy数函​​​​​​​​foo.setBar(123);​​​​​​​​foo.setBar(456, ​​​​'another param'​​​​);​​​​​​​​});​​​​​​​​it(​​​​"测试foo数函是不是被调用过"​​​​, ​​​​function​​​​() {​​​​​​​​expect(foo.setBar).toHaveBeenCalled();​​​​​​​​});​​​​​​​​it(​​​​"测试foo数函被调用的次数"​​​​, ​​​​function​​​​() {​​​​​​​​expect(foo.setBar.calls.length).toEqual(2);​​​​​​​​});​​​​​​​​it(​​​​"测试foo数函被调用时传入的参数"​​​​, ​​​​function​​​​() {​​​​​​​​expect(foo.setBar).toHaveBeenCalledWith(123);​​​​​​​​expect(foo.setBar).toHaveBeenCalledWith(456, ​​​​'another param'​​​​);​​​​​​​​});​​​​​​​​it(​​​​"上一次被调用的参数"​​​​, ​​​​function​​​​() {​​​​​​​​expect(foo.setBar.mostRecentCall.args[0]).toEqual(456);​​​​​​​​});​​​​​​​​it(​​​​"有所被调用的情况存在一个数组里"​​​​, ​​​​function​​​​() {​​​​​​​​expect(foo.setBar.calls[0].args[0]).toEqual(123);​​​​​​​​});​​​​​​​​it(​​​​"数函并未真的执行"​​​​, ​​​​function​​​​() { ​​​​​​​​expect(bar).toBeNull(); ​​​​​​​​});​​​​});​


Spy addCallThrough 数函监视器,但数函真的执行



​describe(​​​​"对spy数函的测试,数函真的执行"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​foo, bar, fetchedBar;​​​​​​​​beforeEach(​​​​function​​​​() {​​​​​​​​foo = {​​​​​​​​setBar: ​​​​function​​​​(value) {​​​​​​​​bar = value;​​​​​​​​},​​​​​​​​getBar: ​​​​function​​​​() {​​​​​​​​return​​​​ bar;​​​​​​​​}​​​​​​​​};​​​​​​​​//spyOn(foo, "setBar"); //如果加上这句,setBar不真的执行,后两个spec不通过​​​​​​​​spyOn(foo, ​​​​'getBar'​​​​).andCallThrough();​​​​​​​​foo.setBar(123);​​​​​​​​fetchedBar = foo.getBar();​​​​​​​​});​​​​​​​​it(​​​​"测试foo中getBar数函是不是被调用过"​​​​, ​​​​function​​​​() {​​​​​​​​expect(foo.getBar).toHaveBeenCalled();​​​​​​​​});​​​​​​​​it(​​​​"foo中setBar数函真的执行了"​​​​, ​​​​function​​​​() {​​​​​​​​expect(bar).toEqual(123);​​​​​​​​});​​​​​​​​it(​​​​"foo中getBar数函真的执行了"​​​​, ​​​​function​​​​() {​​​​​​​​expect(fetchedBar).toEqual(123);​​​​​​​​});​​​​});​



​describe(​​​​"A spy, when faking a return value"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​foo, bar, fetchedBar;​​​​​​​​beforeEach(​​​​function​​​​() {​​​​​​​​foo = {​​​​​​​​setBar: ​​​​function​​​​(value) {​​​​​​​​bar = value;​​​​​​​​},​​​​​​​​getBar: ​​​​function​​​​() {​​​​​​​​return​​​​ bar;​​​​​​​​}​​​​​​​​};​​​​​​​​spyOn(foo, ​​​​'getBar'​​​​).andReturn(745); ​​​​//指定getBar数函返回745​​​​​​​​foo.setBar(123);​​​​​​​​fetchedBar = foo.getBar();​​​​​​​​});​​​​​​​​it(​​​​"测试foo中getBar数函是不是被调用过"​​​​, ​​​​function​​​​() {​​​​​​​​expect(foo.getBar).toHaveBeenCalled();​​​​​​​​});​​​​​​​​it(​​​​"不影响未被监视的其它数函"​​​​, ​​​​function​​​​() {​​​​​​​​expect(bar).toEqual(123);​​​​​​​​});​​​​​​​​it(​​​​"指定的返回值745"​​​​, ​​​​function​​​​() {​​​​​​​​expect(fetchedBar).toEqual(745);​​​​​​​​});​​​​});​


Spy addCallFake 替代被监视的数函,原数函不执行



​describe(​​​​"替代被监视的数函,原数函不执行"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​foo, bar, fetchedBar;​​​​​​​​beforeEach(​​​​function​​​​() {​​​​​​​​foo = {​​​​​​​​setBar: ​​​​function​​​​(value) {​​​​​​​​bar = value;​​​​​​​​},​​​​​​​​getBar: ​​​​function​​​​() {​​​​​​​​alert(​​​​"frostbelt"​​​​);​​​​​​​​return​​​​ bar;​​​​​​​​}​​​​​​​​};​​​​​​​​spyOn(foo, ​​​​'getBar'​​​​).andCallFake(​​​​function​​​​() {​​​​​​​​return​​​​ 1001;​​​​​​​​});​​​​​​​​foo.setBar(123);​​​​​​​​fetchedBar = foo.getBar();​​​​​​​​});​​​​​​​​it(​​​​"测试foo中getBar数函是不是被调用过"​​​​, ​​​​function​​​​() {​​​​​​​​expect(foo.getBar).toHaveBeenCalled();​​​​​​​​});​​​​​​​​it(​​​​"不影响未被监视的其它数函"​​​​, ​​​​function​​​​() {​​​​​​​​expect(bar).toEqual(123);​​​​​​​​});​​​​​​​​it(​​​​"getBar被addCallFake指定的匿名数函代替,getBar不执行"​​​​, ​​​​function​​​​() {​​​​​​​​expect(fetchedBar).toEqual(1001);​​​​​​​​});​​​​});​


如果你没有什么可监视的又实在想监视一下,该咋办?自己create一个被监视数函。。

jasmine.createSpy(functionId)



​describe(​​​​"自己造一个被监视数函。啊,我凌乱了。。"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​whatAmI;​​​​​​​​beforeEach(​​​​function​​​​() {​​​​​​​​whatAmI = jasmine.createSpy(​​​​'whatAmI'​​​​);​​​​​​​​whatAmI(​​​​"I"​​​​, ​​​​"am"​​​​, ​​​​"a"​​​​, ​​​​"spy"​​​​);​​​​​​​​});​​​​​​​​it(​​​​"有个id,是createSpy的传入数函,于用报错"​​​​, ​​​​function​​​​() {​​​​​​​​expect(whatAmI.identity).toEqual(​​​​'whatAmI'​​​​)​​​​​​​​});​​​​​​​​it(​​​​"是不是被调用"​​​​, ​​​​function​​​​() {​​​​​​​​expect(whatAmI).toHaveBeenCalled();​​​​​​​​});​​​​​​​​it(​​​​"被调用的次数"​​​​, ​​​​function​​​​() {​​​​​​​​expect(whatAmI.calls.length).toEqual(1);​​​​​​​​});​​​​​​​​it(​​​​"被调用的参数"​​​​, ​​​​function​​​​() {​​​​​​​​expect(whatAmI).toHaveBeenCalledWith(​​​​"I"​​​​, ​​​​"am"​​​​, ​​​​"a"​​​​, ​​​​"spy"​​​​);​​​​​​​​});​​​​​​​​it(​​​​"最近一次被调用"​​​​, ​​​​function​​​​() {​​​​​​​​expect(whatAmI.mostRecentCall.args[0]).toEqual(​​​​"I"​​​​);​​​​​​​​});​​​​});​


有时要需监视一个象对的很多方法,用createSpyObj添加方法数组

jasmine.createSpyObj(obj, methodArray)



​describe(​​​​"有时要需监视一个象对的很多个方法,用createSpyObj添加数组"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​tape;​​​​​​​​beforeEach(​​​​function​​​​() {​​​​​​​​tape = jasmine.createSpyObj(​​​​'tape'​​​​, [​​​​'play'​​​​, ​​​​'pause'​​​​, ​​​​'stop'​​​​, ​​​​'rewind'​​​​]);​​​​​​​​tape.play();​​​​​​​​tape.pause();​​​​​​​​tape.rewind(0);​​​​​​​​});​​​​​​​​it(​​​​"tape象对的这四个方法已被定义"​​​​, ​​​​function​​​​() {​​​​​​​​expect(tape.play).toBeDefined();​​​​​​​​expect(tape.pause).toBeDefined();​​​​​​​​expect(tape.stop).toBeDefined();​​​​​​​​expect(tape.rewind).toBeDefined();​​​​​​​​});​​​​​​​​it(​​​​"四个方法是不是被调用"​​​​, ​​​​function​​​​() {​​​​​​​​expect(tape.play).toHaveBeenCalled();​​​​​​​​expect(tape.pause).toHaveBeenCalled();​​​​​​​​expect(tape.rewind).toHaveBeenCalled();​​​​​​​​expect(tape.stop).not.toHaveBeenCalled();​​​​​​​​});​​​​​​​​it(​​​​"被调用时传入的参数"​​​​, ​​​​function​​​​() {​​​​​​​​expect(tape.rewind).toHaveBeenCalledWith(0);​​​​​​​​});​​​​});​


jasmine.any 型类判断。instanceof



​describe(​​​​"型类匹配"​​​​, ​​​​function​​​​() {​​​​​​​​it(​​​​"相当于instanceof"​​​​, ​​​​function​​​​() {​​​​​​​​expect({}).toEqual(jasmine.any(Object));​​​​​​​​expect(12).toEqual(jasmine.any(Number));​​​​​​​​});​​​​​​​​it(​​​​"也可以于用spy"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​foo = jasmine.createSpy(​​​​'foo'​​​​);​​​​​​​​foo(12, ​​​​function​​​​() {​​​​​​​​return​​​​ true​​​​​​​​});​​​​​​​​expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));​​​​​​​​//foo被调用时的参数 型类判断​​​​​​​​});​​​​});​


jasmine.Clock.useMock() jasmine自己控制间时,实现异步调试,增长等待

jasmine.Clock.tick(n:uint) 向前n毫秒



​describe(​​​​"jasmine自己控制间时,实现异步调试,增长等待"​​​​, ​​​​function​​​​() {​​​​​​​​var​​​​timerCallback; ​​​​​​​​beforeEach(​​​​function​​​​() {​​​​​​​​timerCallback = jasmine.createSpy(​​​​'timerCallback'​​​​);​​​​​​​​jasmine.Clock.useMock();​​​​​​​​}); ​​​​​​​​it(​​​​"setTimeout"​​​​, ​​​​function​​​​() {​​​​​​​​setTimeout(​​​​function​​​​() {​​​​​​​​timerCallback();​​​​​​​​}, 100);​​​​​​​​expect(timerCallback).not.toHaveBeenCalled();​​​​​​​​jasmine.Clock.tick(101);​​​​​​​​expect(timerCallback).toHaveBeenCalled();​​​​​​​​});​​​​​​​​it(​​​​"setInterval"​​​​, ​​​​function​​​​() {​​​​​​​​setInterval(​​​​function​​​​() {​​​​​​​​timerCallback();​​​​​​​​}, 100);​​​​​​​​expect(timerCallback).not.toHaveBeenCalled();​​​​​​​​jasmine.Clock.tick(101);​​​​​​​​expect(timerCallback.callCount).toEqual(1);​​​​​​​​jasmine.Clock.tick(50);​​​​​​​​expect(timerCallback.callCount).toEqual(1);​​​​​​​​jasmine.Clock.tick(50);​​​​​​​​expect(timerCallback.callCount).toEqual(2);​​​​​​​​});​​​​});​


注:在种这境环下setTimeout和setInterval的callback为同步的,系统间时不再影响执行

runs(function) waitsFor(function, message, millisec) Jasmine异步调试 按自己的理解写个例子



​describe(​​​​"jasmine异步调试,对ajax结果的言断"​​​​, ​​​​function​​​​(){​​​​​​​​var​​​​data, flag = ​​​​false​​​​;​​​​​​​​it(​​​​"ajax是不是按时返回了确正结果"​​​​, ​​​​function​​​​(){​​​​​​​​runs(​​​​function​​​​(){​​​​​​​​$.post(​​​​​​​​url,​​​​​​​​{},​​​​​​​​function​​​​(data){​​​​​​​​flag = ​​​​true​​​​;​​​​​​​​data = data.someAttr;​​​​​​​​}​​​​​​​​);​​​​​​​​});​​​​​​​​waitsFor(​​​​function​​​​(){ ​​​​//flag为true或到2秒时执行 2秒内返回true则执行最后一个runs,到时未返回则本spec出错,返回第二个参数错误信息​​​​​​​​return​​​​ flag;​​​​​​​​}, ​​​​"ajax在指定间时2秒内未返回"​​​​, 2000);​​​​​​​​runs(​​​​function​​​​(){ ​​​​//直到waitsFor返回true时执行​​​​​​​​expect(data).toEqual(​​​​"someThing"​​​​);​​​​​​​​})​​​​​​​​});​​​​});​


注:it是一个spec,含包

runs(function)

waitsFor(function, message, millsec)

runs(function)

第一个runs里有一些异步的代码

waitsFor中的funciton如果在millsec内返回true,执行最后一个runs

如果在millsec内不能返回true,spec不通过,显示错误信息message

代码:

测试单元测试javascript单元测试及框架介绍_javascript_08


e) 用应

在测试的页面里加入以下代码:



​<script type=​​​​"text/javascript"​​​​>​​​​​​​​(​​​​function​​​​() {​​​​​​​​var​​​​jasmineEnv = jasmine.getEnv();​​​​​​​​jasmineEnv.updateInterval = 1000;​​​​​​​​var​​​​trivialReporter = ​​​​new​​​​ jasmine.TrivialReporter();​​​​​​​​jasmineEnv.addReporter(trivialReporter);​​​​​​​​jasmineEnv.specFilter = ​​​​function​​​​(spec) {​​​​​​​​return​​​​trivialReporter.specFilter(spec);​​​​​​​​};​​​​​​​​var​​​​currentWindowOnload = window.onload;​​​​​​​​window.onload = ​​​​function​​​​() {​​​​​​​​if​​​​ (currentWindowOnload) {​​​​​​​​currentWindowOnload();​​​​​​​​}​​​​​​​​execJasmine();​​​​​​​​};​​​​​​​​function​​​​execJasmine() {​​​​​​​​jasmineEnv.execute();​​​​​​​​}​​​​​​​​})();​​​​</script>​


导入jasmine.css jasmine.js jasmine-html.js

src.js(源代码) test.js(存放describes)


l JsTestDriver

a) 简介

JsTestDriver是一个JavaScript单元测试工具,易于与持续构建系统相集成并能够在多个浏览器上运行测试轻松实现TDD风格的发开。当在项目中配置好JsTestDriver以后,如同junit测试java件文一般,JsTestDriver可以直接通过运行js件文来停止单元测试。JsTestDriver框架本身就是JAVA的jar包,要需在本地运行并监听一个端口。

b) 点优

可以一次测试多个浏览器,用应方法是在动启服务时可以将多个浏览器的路径作为参数传进去。可以在多台机器上的浏览器中运行,括包移动设备。

测试运行得很快,因为不要需将结果添加到DOM中呈现出来,它们能够同时在任意多的浏览器中运行,未修改的件文浏览器会从缓存提取。

不要需HTML配件件文,仅仅只需提供一个或多个脚本和测试脚本,测试运行器运行时会创建一个空件文。

能很便方的和Ant、Maven等停止集成停止主动化测试,也可以便方和Jekins等持续集成工具停止集成,可以生成测试结果的XML文档。

有Eclipse和IntelliJ插件,可以很便方的在这两个IDE中停止测试,和JUnit很像。

支撑其它测试框架,可以测试其它测试框架写的测试代码,比如有对应的插件可以将QUnit和Jasmine测试代码转换成JsTestDriver的测试代码。

c) 足不

不能在浏览器中测试,只能通过主动化工具或控制台运行。生成的结果不够直观。

安装用应稍微有点费事,赖依于JAVA境环。

d) API

assert(msg, value)

assertTrue(msg, value)

assertFalse(msg, value)

assertEquals(msg, expected, actual)

assertNotEquals(msg, expected, actual)

assertSame(msg, expected, actual)

assertNotSame(msg, expected, actual)

assertNull(msg, value)

assertNotNull(msg, value)

assertUndefined(msg, value)

assertNotUndefined(msg, value)

assertNaN(msg, number)

assertNotNaN(msg, number)

assertException(msg, callback, type)

assertNoException(msg, callback)

assertArray(msg, arrayLike)

assertTypeOf(msg, type, object)

assertBoolean(msg, value)

assertFunction(msg, value)

assertNumber(msg, value)

assertObject(msg, value)

assertString(msg, value)

assertMatch(msg, pattern, string)

assertNoMatch(msg, pattern, string)

assertTagName(msg, tagName, element)

assertClassName(msg, className, element)

assertElementId(msg, id, element)

assertInstanceOf(msg, constructor, object)

assertNotInstanceOf(msg, constructor, object)

API按字面意思理解即可,不一一标注

e) 用应

前提:

安装java境环

下载JsTestDriver.jar

目录:

JsTestDriver.jar

jsTestDriver.conf //配置件文,默认称名,如果用其它称名,要需指定config参数

src

----src.js

test

----test.js

jsTestDriver.conf:

测试单元测试javascript单元测试及框架介绍_sed_09

src.js:

测试单元测试javascript单元测试及框架介绍_html_10

test.js:

测试单元测试javascript单元测试及框架介绍_javascript_11

像java的JUnit一样,测试方法名要以”test”开头,如:”testXXXX”

测试步骤:



  1. cmd 进入目录
  2. 运行命令”java –jar JsTestDriver.jar –port 9876”
  3. 打开页面http://localhost:9876,点击“捕获浏览器”。
  4. 新打开一个终端,运行命令”java –jar JsTestDriver.jar –tests all”,运行有所测试用例
  5. 也可以单独运行某一个用例,如运行命令” java –jar JsTestDriver.jar –tests addTest.testA”


l FireUnit

a) 简介

FireUnit是一个基于Firebug的Javascript的单元测试框架。单简说来,FireUnit给Firebug增长了一个标签面板,并提供了一些单简的JavaScript API来记录和查看测试。

b) 点优

单简易用

c) 足不

能功不多,测试代码常常写在源码里,虽然可以实时地看到效果,但耦合太强,不易理清

只运行在Firefox下

d) API

经常使用:

fireunit.ok(condition, message) true/false

fireunit.compare(actual, expect, message) 是不是等相

fireunit.reCompare(regexp, string, message) 字符串是不是与正则式匹配

fireunit.testDone(); 执行以上的测试,在Firebug的新标签test中显示结果

其它:

fireunit.runTests(“test2.html”, “test3.html”) 一起运行多个页面的测试(每一个件文都含有一些独立的测试)

fireunit.log(message) 打印log,但似乎不管用

fireunit.id(id) 相当与document.getElementById

fireunit.click(element) 模拟触发element的click事件

fireunit.focus(element) 模拟触发element的focus事件

fireunit.mouseDown(element) 模拟触发element的mouseDown事件

但看代码明明是click事件

测试单元测试javascript单元测试及框架介绍_java_12

在FF下,只是执行了node.click();不会触发onmousedown,只会触发onclick。已验证

fireunit.value(element) 修改element的value

fireunit.key(element, key) 模拟触发element的key事件

例:

var input = document.getElementsByTagName(“input”)[0];

fireunit.key(input, “a”);

没用过:

fireunit.browser

fireunit.test

fireunit.forceHTTP

fireunit.registerPathHandler

fireunit.panel

fireunit.privilege

e) 用应

在FF下装Firebug和FireUnit

直接在代码里写测试,就像console.log();


4. 小结

QUnit框架单简便方,测试界面直观详细

Jasmine能功强大,风格也单简明了,符合前端发开者的编程习惯,推荐

JsTestDriver可以和QUnit等框架结合,可以同时测多个浏览器。但安装复杂,只能在控制台显示,不友好,不够清晰

FireUnit小巧灵活,加上Firebug的人气该应很受欢迎

如果要需停止主动化测试, 多了解一下Jasmine和JsTestDriver,本文未深入。