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单元测试框架详细列表
javascript单元测试(转)的更多相关文章
-
javascript单元测试框架mochajs详解
关于单元测试的想法 对于一些比较重要的项目,每次更新代码之后总是要自己测好久,担心一旦上线出了问题影响的服务太多,此时就希望能有一个比较规范的测试流程.在github上看到牛逼的javascript开 ...
-
初窥Javascript单元测试,附带掌握一门新技能的学习方式。
之前没感觉要学啥单元测试,项目中测试都是手动测的,但也没觉的啥,但最近看文章和招聘上也多多少少有这方面的需求,于是网上搜索了一下,只找到了一些文章,但介绍的都不是很详细或者说比较复杂,满满的伤,虽然看 ...
-
JavaScript单元测试框架JsUnit基本介绍和使用
JavaScript单元测试框架JsUnit基本介绍和使用 XUnit framework XUnit是一套标准化的独立于语言的概念和结构集合,用于编写和运行单元测试(Unit tests). 每一个 ...
-
JavaScript单元测试框架-Jasmine
Jasmine的开发团队来自PivotalLabs,他们一开始开发的JavaScript测试框架是JsUnit,来源于著名的JAVA测试框架JUnit.JsUnit是xUnit的JavaScript实 ...
-
Javascript单元测试框架比较Qunit VS Jasmine
Javascript单元测试框架比较Qunit VS Jasmine 工欲行其事必先利其器,好的单元测试框架是TDD成功的一半.Javascript优秀的测试框架很多, 包括Jasmine,Qunit ...
-
(译)学习如何构建自动化、跨浏览器的JavaScript单元测试
作者:Philip Walton 译者:Yeaseon 原文链接:点此查看 译文仅供个人学习,不用于任何形式商业目的,转载请注明原作者.文章来源.翻译作者及链接,版权归原文作者所有. ___ 我们都知 ...
-
[转]javascript单元测试
1. 什么是单元测试 在计算机编程中,单元测试(又称为模块测试)是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作.程序单元是应用的最小可测试部件.在过程化编程中,一个单元就是单 ...
-
javascript单元测试框架mochajs详解(转载)
章节目录 关于单元测试的想法 mocha单元测试框架简介 安装mocha 一个简单的例子 mocha支持的断言模块 同步代码测试 异步代码测试 promise代码测试 不建议使用箭头函数 钩子函数 钩 ...
-
前端自动化测试工具--使用karma进行javascript单元测试(转)
Karma+Jasmine+PhantomJS组合的前端javascript单元测试工具. 1.介绍 Karma是由Google团队开发的一套前端测试运行框架,karma会启动一个web服务器,将js ...
随机推荐
-
Hypernetes简介
好久没有更新博客了,今天给大家介绍下最近在Hypernetes上做的工作,这个也是之前在微信群里的一个分享. Hypernetes是一个真正多租户的Kubernetes Distro. Hyperne ...
-
art-template用户注册方法
应用场景nodejs Express框架,使用art-template模板引擎. 后台注册方法代码: var template = require('art-template'); template. ...
-
mysql 调用外部程序
一.下载 lib_mysqludf_sys: 下载地址:https://github.com/mysqludf/repositories 二.配置与使用: 1.解压之后,已经有了我们需要的 lib_m ...
-
break语句
//输入年月,不正确重新输入 for (; ; ) { Console.WriteLine("输入年份:"); int year = int.Parse(Console.ReadL ...
-
MongoDB学习笔记-数据库命令
概念 数据库命令(database command)是一种非常特殊类型的查询.文档的创建.更新.删除及查询都属于数据库命令的范畴,它还包含管理性的任务(比如关闭服务器和克隆数据库).统计数据及执行聚合 ...
-
前端面试题(JS篇)
原题地址:http://handyxuefeng.blog.163.com/blog/static/454521722013111714040259/ 好吧,最近打算换工作,所以关注比较多的是面试题, ...
-
Vue基础概念,学习环境等
前提: 你已有 HTML.CSS 和 JavaScript 中级前端知识. 概念: Vue.js(读音 /vjuː/,类似于 view) 是一套构建用户界面的渐进式框架.与其他重量级框架不同的是,Vu ...
-
2019.03.25 NOIP训练 匹配(match)(贪心)
题意简述: 思路: 直接考虑把人和物品都看成二维平面上面的a,ba,ba,b两类点,然后一个aaa和bbb匹配的条件是xa≤xb&&ya≤ybx_a\le x_b\&\& ...
-
Css单位px,rem,em,vw,vh的区别
px px就是pixel像素的缩写,相对长度单位,网页设计常用的基本单位.像素px是相对于显示器屏幕分辨率而言的 em em是相对长度单位.相对于当前对象内文本的字体尺寸(参考物是父元素的font-s ...
-
rman 示例
背景 rman,听这名字,好像有点真的男人意思.这玩意其实也简单,只是老忘,作为一个oracle dba爱好者,怎么少了rman呢,这个好像是oracle体系的最后一环,把它掌握了,就完成oracle ...