javascript单元测试
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
src.js里是我们要测试的一些函数
test.js里放我们的测试
打开test.html,显示:
如果期望值与函数执行的结果不一致,会报错:
test.js
test.html显示:
期望值与结果不符,测试不通过。
与浏览器自动化测试工具集成的接口:
都是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:所有代码的执行时间
API及下载:http://api.qunitjs.com/
参考:http://www.weakweb.com/articles/255.html
http://www.iteye.com/topic/981253
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); });
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); }); });
Spy andReturn 函数监视器,函数不真的执行。指定监视的函数的返回值
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
原文代码:
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)
参考:http://www.weakweb.com/articles/255.html
http://pivotal.github.com/jasmine/
下载:https://github.com/pivotal/jasmine/downloads
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:
src.js:
test.js:
像java的JUnit一样,测试方法名要以”test”开头,如:”testXXXX”
测试步骤:
- cmd 进入目录
- 运行命令”java –jar JsTestDriver.jar –port 9876”
- 打开页面http://localhost:9876,点击“捕获浏览器”。
- 新打开一个终端,运行命令”java –jar JsTestDriver.jar –tests all”,运行所有测试用例
- 也可以单独运行某一个用例,如运行命令” java –jar JsTestDriver.jar –tests addTest.testA”
安装:http://code.google.com/p/js-test-driver/wiki/UsingTheEclipsePlugin
下载:http://code.google.com/p/js-test-driver/
参考:《测试驱动的JavaScript开发》
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事件
在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();
参考:https://github.com/jeresig/fireunit/wiki/internals
http://hi.baidu.com/dearhwj/blog/item/1d1e0dbfc380f80219d81f35.html
4. 小结
QUnit框架简单方便,测试界面直观详细
Jasmine功能强大,风格也简单明了,符合前端开发者的编程习惯,推荐
JsTestDriver可以和QUnit等框架结合,可以同时测多个浏览器。但安装复杂,只能在控制台显示,不友好,不够清晰
FireUnit小巧灵活,加上Firebug的人气应该很受欢迎
如果需要进行自动化测试, 多了解一下Jasmine和JsTestDriver,本文未深入。
5. 附注
JavaScript单元测试框架详细列表