- TDD通过边测试边编写代码,然后重构来避免重构所引起的谬误
- 经过自动化测试和持续集成工具,随时保持可以颁发
-
TDD第一步:
1. 需求分解 2. 将需求转化成测试 3. 写一个失败的测试 4. 逐步通过测试,再写一个测试 5. 开始消除重复代码 (由于这个时候有测试在了,所以不用担心更改会引起集成错误)
### 看到此间感觉在境内公司一度很难落成那么些了,因为时间很难让您去做这个工作
-
相互测试,并不表明结果的不错,而是印证代码与其搭档对象的交互行为的不利
-
重构代码的时候不要一向用调试器调试,而是要把代码分为一个严苛地软件开发活动
- 规定变更点
- 确定测试点
- 蒙面测试点
- 修改代码
- 重构代码
### 先分析程序再写测试再重构,以前都搞反了,先重构再写测试所以很难去保证重构后代码正确,因为思维方向就狼狈,先重构再测试时依照自己的笔触来写测试,更倾向于为了通过测试而写测试,而 先测试再重构思路更倾向于按照工作来拓展测试
-
数据库测试,增量式DDL脚本。一次只添加一个列或者一张表,每个步骤都足以回滚
- 数据库测试使用脚本或者其余措施添加进数据,然后举办测试
- 利用教程,测试驱动开发的主意。TDD通过边测试边编写代码,然后重构来防患重构所引起的一无所能
- 通过自动化测试和相连集成工具,随时保持能够发表
-
TDD第一步:
1. 需求分解 2. 将需求转化成测试 3. 写一个失败的测试 4. 逐步通过测试,再写一个测试 5. 开始消除重复代码 (由于这个时候有测试在了,所以不用担心更改会引起集成错误)
### 看到那里感觉在境内集团曾经很难落到实处那几个了,因为时间很难让你去做这几个工作
-
相互测试,并不表达结果的不利,而是评释清码与其合作对象的互动行为的正确性
-
重构代码的时候绝不直接用调试器调试,而是要把代码分为一个严厉地软件开发活动
- 确定变更点
- 确定测试点
- 覆盖测试点
- 修改代码
- 重构代码
### 先分析程序再写测试再重构,以前都搞反了,先重构再写测试所以很难去承保重构后代码正确,因为思维方向就窘迫,先重构再测试时遵从自己的思绪来写测试,更赞成于为了通过测试而写测试澳门金沙国际 ,,而 先测试再重构思路更倾向于按照业务来拓展测试
-
数据库测试,增量式DDL脚本。一遍只添加一个列或者一张表,每个步骤都可以回滚
- 数据库测试使用脚本或者其余办法添加进数据,然后开展测试
前言
乘机前端开发的复杂度与日俱增,前端工程师写出来的JS代码变得进一步粗大,从而使项目变的重合,维护的资金及难度不断加大,合营迭代开发已经很难保险代码的作用性不被毁损。所以在那种意况下,考虑引入一款前端的单元测试框架,来为代码保驾护航是一件很重点的政工。
出处:
如何是单元测试
可能我们在看有些产品书籍的时候,当介绍一个序列时,会禁不住说到测试。
比如,单元测试,函数测试,或是TDD,BDD等测试方式。
那到底什么样是单元测试呢?
单元测试,单元,测试。
怎么着是单元(unit)?单元就是对立独立的效率模块。一个完好无损的、模块化的次序,都是由许多单元构成,单元完结自己的天职、然后与其它单元举办互动,最后协同达成全套程序成效。
什么是测试?测试就是测试,判断测试对象对于某个特定的输入有没有预期的出口。
故而怎样是单元测试?就是对组合程序的各类单元进行测试(灰霾)。工程上的一个共识是,要是程序的各类模块都是科学的、模块与模块的接连是不利的、那么程序基本上就会是不易的。
由此单元测试就是那般一个概念,一种努力有限协助构成程序的各类模块的没错,从而确保一切程序的正确的方法论。
简不难单,单元测试的目的就是来确保你写的JS模块可以成功职责并且没有出现bug,即使你的代码在单元测试的长河中,须要引入多个模块时,那注脚你测试的珍惜点模块的耦合度相对比较高,也就表示你要重构代码了。
新春佳节前的一篇那多少个炒作过度的技能和概念中对便捷和中国ThoughtWorks的责难引发了无数争持不休,也搅乱了中国ThoughtWorks集团给我发来了邮件想来找我公开聊聊。对于Agile的Fans们,意料之中地也对本人进行了重重质问和批评。我也回复了很多评价。可是,我的这么些回复都是关于中华ThoughtWorks咨询师以及其提问的办法的。我对Agile方法论中的具体内容评价的不是不少,所以,我想不妨啄磨一下Agile方法论中的具体的实践(从前本站也商讨过结对编程的利与弊)。
单元测试的方式
单元测试的方式为主分为:TDD 和 BDD。
TDD 全称是 Test-driven development,即测试驱动开发。
TDD
的原理是在开发效益代码在此以前,先编制单元测试用例代码,测试代码确定必要编制什么产品代码。
TDD
的基本思路就是通过测试来推进任何开发的拓展,但测试驱动开发并不只是仅仅的测试工作,而是把须要分析,设计,品质控制量化的进程。
TDD
首先考虑选取必要(对象、功用、进度、接口等),紧借使编辑测试用例框架对效益的经过和接口进行统筹,而测试框架可以不断拓展验证。
常见的TDD测试步骤是:
先写测试
再写代码
测试
重构
通过
BDD 全称是: Behavior-Driven development,即作为使得开发。
BDD 的采取场景就是给一些 QA
工程师使用的,他用她的言语和您举行交换,即他会开展局地测试用例,然后假如经过则印证,他现已依赖你了。
而 BDD 与 TDD
的要紧分裂是,使得非程序人士也能插手到测试用例的编辑中来,大大下落了客户、用户、项目领导与开发者之间往来翻译的血本。所以BDD尤其讲究工作需求而不是技术,而眼前在大部分同盟社内部,经常使用的是BDD测试。而大家本文的机要就是讲一款BDD情势的测试框架,那就是
Jasmine。
那就是说,这一次就说说TDD吧,那是ThoughtWorks中国和Agile的Fans们最欢愉的事物了。我在原本的那篇文章中,我把TDD从过度炒作的技巧剔除了出来,因为自身要么觉得TDD有些道理的,但是,回想自己的经历,我也并不是很喜欢TDD。我那篇小说是想告知大家,TDD并不曾看起来的那么美,而且丰盛不便掌控,并且,那个艺术是有悖论之处的。
什么是Jasmine
第一,贾斯敏是一款JavaScript 测试框架,它不依靠于其他任何 JavaScript
组件。它有干净清晰的语法,让您可以很粗略的写出测试代码。
附带,Jasmine官网介绍其中开篇第一句话是“Jasmine is a behavior-driven
development framework for testing JavaScript
code.”,首要的趣味乃是它是一款BDD格局的测试框架,也就是行为使得开发,同样它是一种高效软件开发的技巧。
BDD 更像是一种集体的约定,javascript
单元测试,也许对于你本身(开发该脚本的前端)意义不是越发非凡,但对此整个集体,整个项目来说就是一种财富
TDD简介
TDD万事俱备Test
Driven
Development,是一种软件开发的流程,其由飞速的“终端编程”引入。其支付进度是从效用必要的test
case开端,先添加一个test case,然后运行具有的test
case看看有不成难题,再落实test case所要测试的功能,然后再运行test
case,查看是还是不是有case退步,然后重构代码,再另行以上步骤。其理念关键是保险两件事:
- 管教所有的必要都能被照顾到。
- 在代码不断充实和重构的长河中,可以检查有着的功用是还是不是正确。
自身不否认TDD的部分实惠的地点,即使我们以Test Case
伊始,那么,大家就能够即时了然大家的代码运行的情状是什么的,那样可以让大家更早地取得大家兑现思路的报告,于是大家更会有信心去重构,去重新设计,从而得以让大家的代码更为科学。
唯独,我想唤起的是,TDD和Unit
Test是两码子事儿。有广大人可能混淆了自动化的Unit
Test(如:XUnit系例)和TDD的软件开发进程。其余,可能还会有人向鼓吹“TDD让您举行自顶向下的筹划艺术”,对此,请参阅本站的《RichardFeynman, 挑衅者号,
软件工程》——NASA的挑衅者号报告您自顶向下设计的危险性。
何以选拔Jasmine
官网文档地址:http://jasmine.github.io/2.3/introduction.html
1、在项目根目录中,开首化 package.json
npm init
2、目录结构:
- src
- index.js
- test
- indexTest.js
package.json
3、安装 karma + jasmine 相关包
npm install -g karma-cli
npm install karma karma-jasmine karma-chrome-launcher jasmine-core --save-dev
4、开启 Karma
karma start
image
手动打开Chrome,输入localhost:9876
image
5、初始化 karma
karma init
image
说明:
-
测试框架:大家当然选jasmine
-
是或不是丰富 Require.js 插件
-
分选浏览器: 大家选Chrome
-
测试文件路径设置,文件可以行使通配符匹配,比如*.js匹配指定目录下拥有的js文件(实际操作中发觉该路线是
karma.conf.js 文件的相对路径,详见下边我付诸的莫过于测试配置及表明) -
在测试文件路径下,须求排除的公文
-
是还是不是同意 Karma 监测文件,yes 代表当测试路径下的文本变化时,Karma
会自动测试
TDD的孤苦之处
上边是多少个自己认为TDD不易于掌控的地点,甚至就不怎么无法(假诺有某某TDD的Fans或是ThoughtWorks的咨询师和你鼓吹TDD,你能够咨询他们下边这个题材)
- 测试范围的确定。TDD开发流程,一般是先写Test Case。Test
Case有诸三种,有Functional的,有Unit的,有Integration的……,最难的是Test
Case要写成什么样的品位呢。- 设若写的太过High Level,那么,当你的Test Case
战败的时候,你不通晓哪个地方出难点了,你得要花不少精力去debug代码。而大家希望的是其可以告诉我是哪位模块出的难点。唯有High
Level的Test Case,岂不就是沃特erfall中的Test环节? - 万一写的太过Low
Level,那么,带来的标题是,你须求花两倍的时光来保险你的代码,一份给test
case,一份给落实的作用代码。 - 除此以外,假设写得太Low
Level,按照Agile的迭代开发以来,你的须求是易变的,很多时候,我们的须求都是开发人士自己做的Assumption。所以,你把Test
Case
写得越细,未来,一旦须要或Assumption爆发变化,你的掩护资金也是成级数大增的。 - 自然,即便自身把一个意义或模块已毕好了,我自然知道Test
的Scope在哪儿,我也理解我的Test
Case需求写成什么样的档次。可是,TDD的悖论就在于,你在完毕从前先把Test
Case就写出来,所以,你怎么能担保你一开头的Test
Case是相符于您前面的代码的?不要忘了,程序员也是在开发的长河中国和日本渐驾驭须要和连串的。即使边贯彻边调整Test
Case,为何不在落成完后再写Test
Case呢?要是是那样的话,那就不是TDD了。
- 设若写的太过High Level,那么,当你的Test Case
- 关注测试而不是安插。那恐怕是TDD的一个弊端,如同《十条正确的编程观点》中所说的一模一样——“Unit
Test won’t help you write the good
code”,在实质上的操作进度中,我看齐不以为奇程序员为了赶工或是应付工作,以致其写的代码是为了满意测试的,而忽视了代码品质和骨子里必要。有时候,当我们重构代码或是fix
bug的时候,甚至招致程序员认为一旦抱有的Test
Case都经过了,代码就是科学的。当然,TDD的粉丝们自然会有上面的辩解:- 能够因而结对编程来保管代码质量。
- 代码一开端就是须求知足作用正确,前边才是重构和调优,而TDD正好让您的重构和优化不会以献身作用为代价。
说的没错,但仅在答辩上。操作起来也许会并不会收获期望的结果。1)“结对编程”其并无法确保结对的两人都不会以满意测试为目标,因为重构或是优化的历程中,一旦程序员看到N多的test
cases 都failed了,人是会如坐针毡的,你会不自然地去fix你的代码以让具有的test
case都经过。2)此外,我不精晓我们怎么编程,我一般的做法是从大局考虑一下种种立竿见影的贯彻方案,对于部分难关必要实际地去编程试试,最终权衡比较,挑选一个最好的方案去完结。而屡屡着急着去已毕某一效益,平常在会造成的是返工,而前面的重构基本上因为早期考虑不足和成为了重写。所以,在实际操作进程中,你会发觉,很多时候的重构寻常意味器重写,因为那几个”非成效性”的须求,你不得不re-design。而re-design往往意味着,你要重写过多Low-Level的Test
Cases,搞得你只敢写High Level的Test Case。
- TDD导致大量的Mock和Stub。相信我,Test
Case并不一定是那么简单的。比如,和其它团体或者系统的接口的连片,或是对贯彻还不是很明白的模块,等等。于是你要求在您的代码中做过多的Mock和Stub,甚至fake一些函数来做模拟,很显明,你须要作大量的
assumption。于是,你意识管理和护卫那些Mock和Stub也成了一种负担,最可怜的是,那不是真正的购并测试,你的Test
Case中的Mock很可能是错的,你要求重写他们。
可能,你会说,尽管是不用TDD,在例行的支付过程中,我们实在必要利用Mock和Stub。没错!的确是如此的,可是,记住,我们是在落到实处代码后来控制哪些地点放一个Mock或Stub,而不是在代码完结前干这些事的。
- Test
Case并不曾想像中的那么不难。和沃特erfall一样,沃特erfall的每一个环节都看重于前方那个环节的不易,假如大家一贯不正确的理解必要,那么对于TDD,Test
Case和我们的Code都会的错的。所以,TDD中,Test
Case是付出中最根本的环节,Test
Case的品质的难题会直接导致软件开发的科学和频率。而TW的咨询师和Agile的Fans们就像天生就认为,TDD比沃特erfall更能准确地打听要求。假使真是如此,用TDD举办须要分析,后面一贯沃特erfall就OK了。
其余,某些Test Case并不一定那么好写,你也许80%的编程时间须求花在某个Test
Case的统筹和贯彻上(比如:测试并发),然后,必要一变,你又得重写Test
Case。有时候,你会发觉写Test
Case其实和夯实在设计没大相径庭,你同一要考虑你Test
Case的正确,伸张性,易读性,易维护性,甚至重用性。若是说大家付出的Test
Case是用来确保大家代码完毕的没错,那么,何人又来担保大家的Test
Case的正确呢?编写Test Case也急需结对或是Code
review吗?软件开发有点像长跑,倘若把能量花在了前半程,后半程在发力就能难了。
莫不,TDD真是过度炒作的,不过,我还真是见过使用TDD开发的正确性的档次,只可是那些项目相比较不难了。越多的图景下,我看齐的是教条式的生硬的TDD,所以,不奇怪地听到了程序员们的埋怨——“自从用了TDD,工作量更大了”。当然,那也不可能怪他们,TDD本来就是很难把控的格局。那里送给软件开发管理者们一句话——“当您的软件开发出现难点的时候,就好像bug-fix一样,主要的事是找到root
cause,然后再case by
case的解决,千万不要因为有题目即将及时换一种新的开发方法”。相信自己,半数以上的难点是人和领导者的题材,不是格局的题材。
以下是 karma.conf.js 的一体化内容:
// Karma configuration
// Generated on Wed Nov 16 2016 14:26:14 GMT+0800 (中国标准时间)
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'src/**/*.js',
'test/**/*.js'
],
// list of files to exclude
exclude: [],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
}
index.js 内容:
function reverse(name){
if(name === 'AAA') return 'BBB';
return name.split("").reverse().join("");
}
indexTest.js 内容:
describe("全部变量,定义测试", function() {
beforeEach(function(){
});
afterEach(function(){
});
it("reverse word", function(){
expect("DCBA").toBe(reverse("ABCD"));
});
});
启动 karma:
karma start
因为大家在配置里设置了在 Chrome 中测试,由此 karma 会自动启动 Chrome
实例,并运行测试用例:
image
倘使大家点击图中的 debug 按钮,进入 debug.html 并按 F12
打开开发者工具,选取 Console 窗口,大家将能看出 jasmine 的履行日志
image
本条时候,说明大家早就安插成功,并且可以进行测试用例编写。
代码覆盖率
假设你还想查看测试的代码覆盖率,大家得以设置karma-coverage插件,安装命令为:
npm install karma-coverage --save-dev
修改 karma.conf.js,扩充覆盖率的陈设:
preprocessors: {
'src/**/*.js': ['coverage']
}
reporters: ['progress', 'coverage']
// add
coverageReporter: {
type: 'html',
dir: 'coverage/'
}
变动如下:
- 在 reporters 中增加 coverage
- preprocessors 中指定 js 文件
- 添加 coverageReporter 节点,将覆盖率报告项目 type 设置为
html,输入目录 dir 指定到你希望的目录中
启动 Karma
karma start
(执行命令后,在配备文件 coverageReporter 节点中指定的 dir
中,我们将找到变化的覆盖率报告,karma-coverage
还生成了一层子文件夹,对应于执行测试的浏览器+版本号+操作系统版本)
image
使用 jasmine-html
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Jasmine Spec Runner v2.4.1</title>
<link rel="shortcut icon" type="image/png" href="../jasmine-2.4.1jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="../jasmine-2.4.1/lib/jasmine-core/jasmine.css">
<script type="text/javascript" src="../jasmine-2.4.1/lib/jasmine-core/jasmine.js"></script>
<script type="text/javascript" src="../jasmine-2.4.1/lib/jasmine-core/jasmine-html.js"></script>
<script type="text/javascript" src="../jasmine-2.4.1/lib/jasmine-core/boot.js"></script>
<!-- 需要测试的js文件及jasmine测试脚本 -->
<script type="text/javascript" src="myFirstJasmineTest.js"></script>
</head>
<body>
</body>
</html>
myFirstJasmineTest
/*
Created by laixiangran on 2015/12/15.
jasmine测试脚本
*/
(function() {
/*
jasmine基本语法介绍:
describe(string, function):可以理解为是一个测试集或者测试包(官方称之为suite),主要功能是用来划分单元测试的,describe是可以嵌套使用的
参数string:描述测试包的信息
参数function:测试集的具体实现,可包含任意代码
it(string, function):测试用例(官方称之为spec)
参数string:描述测试用例的信息
参数function:测试用例的具体实现,可包含任意代码
expect:断言表达式
从以下例子可知:
1、每个测试文件中可以包含多个describe
2、每个describe中可以包含多个it
3、每个it中可以包含多个expect
4、describe可嵌套使用
*/
describe("Jasmine Test 1", function() {
it("a spec with an expectation", function() {
expect(1).toBe(1);
expect(1===1).toBe(true);
expect("a").not.toBe("b");
});
it("an other spec in current suite", function() {
expect(true).toBe(true);
});
});
describe("Jasmine Test 2", function() {
it("nothing", function() {
});
});
describe("Jasmine Test 3", function() {
describe("Jasmine Test 4", function() {
it("b等于b", function() {
expect("b").toBe("b");
});
it("1===1是正确的", function() {
expect(1===1).toBe(true);
});
});
});
/*
* expect的匹配器
* */
describe("Included matchers:", function() {
//"toBe"基本类型判断
it("The 'toBe' matcher compares with ===", function() {
var a = 12;
var b = a;
expect(a).toBe(b);
expect(a).not.toBe(null);
});
//"toEqual"除了能判断基本类型(相当于"toBe"),还能判断对象
describe("The 'toEqual' matcher", function() {
//基本类型判断
it("works for simple literals and variables", function() {
var a = 12;
expect(a).toEqual(12);
});
//对象判断
it("should work for objects", function() {
var foo = {
a: 12,
b: 34
};
var bar = {
a: 12,
b: 34
};
expect(foo).toEqual(bar);
});
});
//"toMatch"使用正则表达式判断
it("The 'toMatch' matcher is for regular expressions", function() {
var message = "foo bar baz";
expect(message).toMatch(/bar/);
expect(message).toMatch("bar");
expect(message).not.toMatch(/quux/);
});
//"toBeDefined"判断是否定义
it("The 'toBeDefined' matcher compares against 'undefined'", function() {
var a = {
foo: "foo"
};
expect(a.foo).toBeDefined();
expect(a.bar).not.toBeDefined();
});
//"toBeUndefined"判断是否是undefined,与"toBeDefined"相反
it("The 'toBeUndefined' matcher compares against 'undefined'", function() {
var a = {
foo: "foo"
};
expect(a.foo).not.toBeUndefined();
expect(a.bar).toBeUndefined();
});
//"toBeNull"判断是否为null
it("The 'toBeNull' matcher compares against null", function() {
var a = null;
var foo = "foo";
expect(null).toBeNull();
expect(a).toBeNull();
expect(foo).not.toBeNull();
});
//"toBeTruthy"判断是否是true
it("The 'toBeTruthy' matcher is for boolean casting testing", function() {
var a, foo = "foo";
expect(foo).toBeTruthy();
expect(a).not.toBeTruthy();
expect(true).toBeTruthy();
});
//"toBeFalsy"判断是否是false
it("The 'toBeFalsy' matcher is for boolean casting testing", function() {
var a, foo = "foo";
expect(a).toBeFalsy();
expect(foo).not.toBeFalsy();
expect(false).toBeFalsy();
});
//"toContain"判断数组是否包含(可判断基本类型和对象)
it("The 'toContain' matcher is for finding an item in an Array", function() {
var a = ["foo", "bar", "baz"];
var b = [{foo: "foo", bar: "bar"}, {baz: "baz", bar: "bar"}];
expect(a).toContain("bar");
expect(a).not.toContain("quux");
expect(b).toContain({foo: "foo", bar: "bar"});
expect(b).not.toContain({foo: "foo", baz: "baz"});
});
//"toBeLessThan"判断值类型的大小,结果若小则为True(也可以判断字符及字符串,以ascii码的大小为判断依据)
it("The 'toBeLessThan' matcher is for mathematical comparisons", function() {
var pi = 3.1415926,
e = 2.78;
expect(e).toBeLessThan(pi);
expect(pi).not.toBeLessThan(e);
expect("a").toBeLessThan("b");
expect("b").not.toBeLessThan("a");
});
//"toBeGreaterThan"判断值类型的大小,结果若大则为True,与toBeLessThan相反(也可以判断字符及字符串,以ascii码的大小为判断依据)
it("The 'toBeGreaterThan' matcher is for mathematical comparisons", function() {
var pi = 3.1415926,
e = 2.78;
expect(pi).toBeGreaterThan(e);
expect(e).not.toBeGreaterThan(pi);
expect("a").not.toBeGreaterThan("b");
expect("b").toBeGreaterThan("a");
});
//"toBeCloseTo"判断数字是否相似(第二个参数为小数精度,默认为2位)
it("The 'toBeCloseTo' matcher is for precision math comparison", function() {
var a = 1.1;
var b = 1.5;
var c = 1.455;
var d = 1.459;
expect(a).toBeCloseTo(b, 0);
expect(a).not.toBeCloseTo(c, 1);
expect(c).toBeCloseTo(d);
});
//"toThrow"判断是否抛出异常
it("The 'toThrow' matcher is for testing if a function throws an exception", function() {
var foo = function() {
return 1 + 2;
};
var bar = function() {
return a + 1;
};
expect(foo).not.toThrow();
expect(bar).toThrow();
});
//"toThrowError"判断是否抛出了指定的错误
it("The 'toThrowError' matcher is for testing a specific thrown exception", function() {
var foo = function() {
throw new TypeError("foo bar baz");
};
expect(foo).toThrowError("foo bar baz");
expect(foo).toThrowError(/bar/);
expect(foo).toThrowError(TypeError);
expect(foo).toThrowError(TypeError, "foo bar baz");
});
});
/*
* "fail"函数能使一个测试用例失败,参数为自定义的失败信息
* */
describe("A spec using the fail function", function() {
var foo = function(x, callBack) {
if (x) {
callBack();
}
};
it("should not call the callBack", function() {
foo(false, function() {
fail("Callback has been called");
});
});
});
/*
Jasmine允许在执行测试集/测试用例的开始前/结束后做一些初始化/销毁的操作。
Setup方法:
beforeAll:每个suite(即describe)中所有spec(即it)运行之前运行
beforeEach:每个spec(即it)运行之前运行
Teardown方法:
afterAll:每个suite(即describe)中所有spec(即it)运行之后运行
afterEach:每个spec(即it)运行之后运行
* */
var globalCount;
describe("Setup and Teardown suite 1", function() {
var suiteGlobalCount;
var eachTestCount;
beforeAll(function() {
globalCount = 0;
suiteGlobalCount = 0;
eachTestCount = 0;
});
afterAll(function() {
suiteGlobalCount = 0;
});
beforeEach(function() {
globalCount++;
suiteGlobalCount++;
eachTestCount++;
});
afterEach(function() {
eachTestCount = 0;
});
it("Spec 1", function() {
expect(globalCount).toBe(1);
expect(suiteGlobalCount).toBe(1);
expect(eachTestCount).toBe(1);
});
it("Spec 2", function() {
expect(globalCount).toBe(2);
expect(suiteGlobalCount).toBe(2);
expect(eachTestCount).toBe(1);
});
});
describe("Setup and Teardown suite 2", function() {
beforeEach(function() {
globalCount += 2;
});
it("Spec 1", function() {
expect(globalCount).toBe(4);
});
});
/*
* 在beforeEach - it - afterEach中,还可以使用this关键字定义变量。需要注意的是,使用this关键字声明的变量,仅在beforeEach - it - afterEach这个过程中传递
* */
describe("Test 'this'", function() {
beforeEach(function() {
this.testCount = this.testCount || 0;
this.testCount++;
});
afterEach(function() {
//this.testCount = 0; //无论是否有这行,结果是一样的,因为this指定的变量只能在每个spec的beforeEach/it/afterEach过程中传递
});
it("Spec 1", function() {
expect(this.testCount).toBe(1);
});
it("Spec 2", function() {
expect(this.testCount).toBe(1);
});
});
/*
在实际项目中,需要由于发布的版本需要选择测试用例包,xdescribe和xit能很方便的将不包含在版本中的测试用例排除在外。
不过xdescribe和xit略有不同:
xdescribe:该describe下的所有it将被忽略,Jasmine将直接忽略这些it,因此不会被运行
xit:运行到该it时,挂起它不执行
* */
xdescribe("Test xdescribe", function() {
it("Spec 1", function() {
expect(1).toBe(1);
});
it("Spec 2", function() {
expect(2).toBe(2);
});
});
describe("Test xit", function() {
it("Spec 1", function() {
expect(1).toBe(1);
});
xit("Spec 2", function() {
expect(2).toBe(1);
});
xit("Spec 3", function() {
expect(3).toBe(3);
});
});
/*
* Spy用来追踪函数的调用历史信息(是否被调用、调用参数列表、被请求次数等)。Spy仅存在于定义它的describe和it方法块中,并且每次在spec执行完之后被销毁。
* 当在一个对象上使用spyOn方法后即可模拟调用对象上的函数,此时对所有函数的调用是不会执行实际代码的。
* 两个Spy常用的expect:
* toHaveBeenCalled: 函数是否被调用
* toHaveBeenCalledWith: 调用函数时的参数
* */
describe("A spy", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
}
};
spyOn(foo, "setBar"); // 在foo对象上添加spy
// 此时调用foo对象上的方法,均为模拟调用,因此不会执行实际的代码
foo.setBar(123); // 调用foo的setBar方法
foo.setBar(456, "another param");
});
it("tracks that the spy was called", function() {
expect(foo.setBar).toHaveBeenCalled(); //判断foo的setBar是否被调用
});
it("tracks all the arguments of its calls", function() {
expect(foo.setBar).toHaveBeenCalledWith(123); //判断被调用时的参数
expect(foo.setBar).toHaveBeenCalledWith(456, "another param");
});
it("stops all execution on a function", function() {
expect(bar).toBeNull(); // 由于是模拟调用,因此bar值并没有改变
});
});
/*
* spyOn().and.callThrough(),告诉Jasmine我们除了要完成对函数调用的跟踪,同时也需要执行实际的代码。
* */
describe("A spy, when configured to call through", function() {
var foo, bar, fetchedBar;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
},
getBar: function() {
return bar;
}
};
spyOn(foo, "getBar").and.callThrough(); // 这里使用了callThrough,这时所有的函数调用为真实的执行
spyOn(foo, "setBar").and.callThrough();
foo.setBar(123);
fetchedBar = foo.getBar();
});
it("tracks that the spy was called", function() {
expect(foo.getBar).toHaveBeenCalled();
});
it("should not effect other functions", function() {
expect(foo.setBar).toHaveBeenCalledWith(123);
expect(bar).toEqual(123); // 由于是真实调用,因此bar有了真实的值
});
it("when called returns the requested value", function() {
expect(fetchedBar).toEqual(123); // 由于是真实调用,fetchedBar也有了真实的值
});
});
/*
* spyOn().and.returnValue(),由于Spy是模拟函数的调用,因此我们也可以强制指定函数的返回值。
* */
describe("A spy, when configured to fake a return value", function() {
var foo, bar, fetchedBar;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
},
getBar: function() {
return bar;
}
};
spyOn(foo, "getBar").and.returnValue(745); // 这将指定getBar方法返回值为745
foo.setBar(123);
fetchedBar = foo.getBar();
});
it("tracks that the spy was called", function() {
expect(foo.getBar).toHaveBeenCalled();
});
it("should not effect other functions", function() {
expect(bar).toEqual(123);
});
it("when called returns the requested value", function() {
expect(fetchedBar).toEqual(745);
});
});
/*
* spyOn().and.callFake(),
* 与returnValue相似,callFake则更进一步,直接通过指定一个假的自定义函数来执行。这种方式比returnValue更灵活,我们可以任意捏造一个函数来达到我们的测试要求。
* */
describe("A spy, when configured with an alternate implementation", function() {
var foo, bar, fetchedBar;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
},
getBar: function() {
return bar;
}
};
spyOn(foo, "getBar").and.callFake(function() {
return 1001;
});
foo.setBar(123);
fetchedBar = foo.getBar();
});
it("tracks that the spy was called", function() {
expect(foo.getBar).toHaveBeenCalled();
});
it("should not effect other functions", function() {
expect(bar).toEqual(123);
});
it("when called returns the requested value", function() {
expect(fetchedBar).toEqual(1001);
});
});
/*
* spyOn().and.throwError(),模拟异常的抛出
* */
describe("A spy, when configured to throw an error", function() {
var foo, bar;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
}
};
spyOn(foo, "setBar").and.throwError("quux");
});
it("throws the value", function() {
expect(function() {
foo.setBar(123)
}).toThrowError("quux");
});
});
/*
* spyOn().and.stub(),回复到原始的spyOn()方法
* */
describe("A spy", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
},
getBar: function(){
return bar;
}
};
spyOn(foo, "setBar").and.callThrough(); // 标记1
spyOn(foo, "getBar").and.returnValue(999); // 标记2
});
it("can call through and then stub in the same spec", function() {
foo.setBar(123);
expect(bar).toEqual(123);
var getValue = foo.getBar();
expect(getValue).toEqual(999);
foo.setBar.and.stub(); // 相当于"标记1"中的代码变为了spyOn(foo, "setBar")
foo.getBar.and.stub(); // 相当于"标记2"中的代码变为了spyOn(foo, "getBar")
bar = null;
foo.setBar(123);
expect(bar).toBe(null);
expect(foo.setBar).toHaveBeenCalled(); // 函数调用追踪并没有被重置
getValue = foo.getBar();
expect(getValue).toEqual(undefined);
expect(foo.getBar).toHaveBeenCalled(); // 函数调用追踪并没有被重置
});
});
/*
* 其他追踪属性:
calls:对于被Spy的函数的调用,都可以在calls属性中跟踪。
.calls.any(): 被Spy的函数一旦被调用过,则返回true,否则为false;
.calls.count(): 返回被Spy的函数的被调用次数;
.calls.argsFor(index): 返回被Spy的函数的调用参数,以index来指定参数;
.calls.allArgs():返回被Spy的函数的所有调用参数;
.calls.all(): 返回calls的上下文,这将返回当前calls的整个实例数据;
.calls.mostRecent(): 返回calls中追踪的最近一次的请求数据;
.calls.first(): 返回calls中追踪的第一次请求的数据;
.object: 当调用all(),mostRecent(),first()方法时,返回对象的object属性返回的是当前上下文对象;
.calls.reset(): 重置Spy的所有追踪数据;
* */
describe("A spy", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
}
};
spyOn(foo, "setBar");
});
//.calls.any(): 被Spy的函数一旦被调用过,则返回true,否则为false;
it("tracks if it was called at all", function() {
expect(foo.setBar.calls.any()).toEqual(false);
foo.setBar();
expect(foo.setBar.calls.any()).toEqual(true);
});
//.calls.count(): 返回被Spy的函数的被调用次数;
it("tracks the number of times it was called", function() {
expect(foo.setBar.calls.count()).toEqual(0);
foo.setBar();
foo.setBar();
expect(foo.setBar.calls.count()).toEqual(2);
});
//.calls.argsFor(index): 返回被Spy的函数的调用参数,以index来指定参数;
it("tracks the arguments of each call", function() {
foo.setBar(123);
foo.setBar(456, "baz");
expect(foo.setBar.calls.argsFor(0)).toEqual([123]);
expect(foo.setBar.calls.argsFor(1)).toEqual([456, "baz"]);
});
//.calls.allArgs():返回被Spy的函数的所有调用参数;
it("tracks the arguments of all calls", function() {
foo.setBar(123);
foo.setBar(456, "baz");
expect(foo.setBar.calls.allArgs()).toEqual([[123],[456, "baz"]]);
});
//.calls.all(): 返回calls的上下文,这将返回当前calls的整个实例数据;
it("can provide the context and arguments to all calls", function() {
foo.setBar(123);
expect(foo.setBar.calls.all()).toEqual([{object: foo, args: [123], returnValue: undefined}]);
});
//.calls.mostRecent(): 返回calls中追踪的最近一次的请求数据;
it("has a shortcut to the most recent call", function() {
foo.setBar(123);
foo.setBar(456, "baz");
expect(foo.setBar.calls.mostRecent()).toEqual({object: foo, args: [456, "baz"], returnValue: undefined});
});
//.calls.first(): 返回calls中追踪的第一次请求的数据;
it("has a shortcut to the first call", function() {
foo.setBar(123);
foo.setBar(456, "baz");
expect(foo.setBar.calls.first()).toEqual({object: foo, args: [123], returnValue: undefined});
});
//.object: 当调用all(),mostRecent(),first()方法时,返回对象的object属性返回的是当前上下文对象;
it("tracks the context", function() {
var spy = jasmine.createSpy("spy");
var baz = {
fn: spy
};
var quux = {
fn: spy
};
baz.fn(123);
quux.fn(456);
expect(spy.calls.first().object).toBe(baz);
expect(spy.calls.mostRecent().object).toBe(quux);
});
//.calls.reset(): 重置Spy的所有追踪数据;
it("can be reset", function() {
foo.setBar(123);
foo.setBar(456, "baz");
expect(foo.setBar.calls.any()).toBe(true);
foo.setBar.calls.reset();
expect(foo.setBar.calls.any()).toBe(false);
});
});
/*
jasmine.createSpy()
* 假如没有函数可以追踪,我们可以自己创建一个空的Spy。
* 创建后的Spy功能与其他的Spy一样:跟踪调用、参数等,但该Spy没有实际的代码实现,这种方式经常会用在对JavaScript中的对象的测试。
* */
describe("A spy, when created manually", function() {
var whatAmI;
beforeEach(function() {
whatAmI = jasmine.createSpy("whatAmI");
whatAmI("I", "am", "a", "spy");
});
it("is named, which helps in error reporting", function() {
expect(whatAmI.and.identity()).toEqual("whatAmI");
});
it("tracks that the spy was called", function() {
expect(whatAmI).toHaveBeenCalled();
});
it("tracks its number of calls", function() {
expect(whatAmI.calls.count()).toEqual(1);
});
it("tracks all the arguments of its calls", function() {
expect(whatAmI).toHaveBeenCalledWith("I", "am", "a", "spy");
});
it("allows access to the most recent call", function() {
expect(whatAmI.calls.mostRecent().args[0]).toEqual("I");
});
});
/*
jasmine.createSpyObj()
* 如果需要spy模拟多个函数调用,可以向jasmine.createSpyObj中传入一个字符串数组,它将返回一个对象,
* 你所传入的所有字符串都将对应一个属性,每个属性即为一个Spy。
* */
describe("Multiple spies, when created manually", function() {
var tape;
beforeEach(function() {
tape = jasmine.createSpyObj('tape', ['play', 'pause', 'stop', 'rewind']);
tape.play();
tape.pause();
tape.rewind(0);
});
it("creates spies for each requested function", function() {
expect(tape.play).toBeDefined();
expect(tape.pause).toBeDefined();
expect(tape.stop).toBeDefined();
expect(tape.rewind).toBeDefined();
});
it("tracks that the spies were called", function() {
expect(tape.play).toHaveBeenCalled();
expect(tape.pause).toHaveBeenCalled();
expect(tape.rewind).toHaveBeenCalled();
expect(tape.stop).not.toHaveBeenCalled();
});
it("tracks all the arguments of its calls", function() {
expect(tape.rewind).toHaveBeenCalledWith(0);
});
});
/*
* jasmine.any()
* 以构造器或者类名作为参数,Jasmine将判断期望值和真实值的构造器是否相同,若相同则返回true。
* */
describe("jasmine.any", function() {
it("matches any value", function() {
expect({}).toEqual(jasmine.any(Object));
expect(12).toEqual(jasmine.any(Number));
});
describe("when used with a spy", function() {
it("is useful for comparing arguments", function() {
var foo = jasmine.createSpy("foo");
foo(12, function() {
return true;
});
expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));
});
});
});
/*
* jasmine.anything()
* 判断只要不是null或undefined的值,若不是则返回true。
* */
describe("jasmine.anything", function() {
it("matches anything", function() {
expect(1).toEqual(jasmine.anything());
});
describe("when used with a spy", function() {
it("is useful when the argument can be ignored", function() {
var foo = jasmine.createSpy('foo');
foo(12, function() {
return false;
});
expect(foo).toHaveBeenCalledWith(12, jasmine.anything());
});
});
});
/*
* jasmine.objectContaining()
* 用来判断对象中是否存在指定的键值属性对。
* */
describe("jasmine.objectContaining", function() {
var foo;
beforeEach(function() {
foo = {
a: 1,
b: 2,
bar: "baz"
};
});
it("matches objects with the expect key/value pairs", function() {
expect(foo).toEqual(jasmine.objectContaining({
bar: "baz"
}));
expect(foo).not.toEqual(jasmine.objectContaining({
c: 37
}));
});
describe("when used with a spy", function() {
it("is useful for comparing arguments", function() {
var callback = jasmine.createSpy("callback");
callback({
bar: "baz"
});
expect(callback).toHaveBeenCalledWith(jasmine.objectContaining({
bar: "baz"
}));
expect(callback).not.toHaveBeenCalledWith(jasmine.objectContaining({
c: 37
}));
});
});
});
/*
* jasmine.arrayContaining()
* 可以用来判断数组中是否有期望的值。
* */
describe("jasmine.arrayContaining", function() {
var foo;
beforeEach(function() {
foo = [1, 2, 3, 4];
});
it("matches arrays with some of the values", function() {
expect(foo).toEqual(jasmine.arrayContaining([3, 1])); // 直接在期望值中使用jasmine.arrayContaining达到目的
expect(foo).not.toEqual(jasmine.arrayContaining([6]));
});
describe("when used with a spy", function() {
it("is useful when comparing arguments", function() {
var callback = jasmine.createSpy("callback"); // 创建一个空的Spy
callback([1, 2, 3, 4]); // 将数组内容作为参数传入Spy中
expect(callback).toHaveBeenCalledWith(jasmine.arrayContaining([4, 2, 3]));
expect(callback).not.toHaveBeenCalledWith(jasmine.arrayContaining([5, 2]));
});
});
});
/*
* jasmine.stringMatching()
* 用来模糊匹配字符串,在jasmine.stringMatching中也可以使用正则表达式进行匹配,使用起来非常灵活。
* */
describe("jasmine.stringMatching", function() {
it("matches as a regexp", function() {
expect({foo: "bar"}).toEqual({foo: jasmine.stringMatching(/^bar$/)});
expect({foo: "foobarbaz"}).toEqual({foo: jasmine.stringMatching("bar")});
});
describe("when used with a spy", function() {
it("is useful for comparing arguments", function() {
var callback = jasmine.createSpy("callback");
callback("foobarbaz");
expect(callback).toHaveBeenCalledWith(jasmine.stringMatching("bar"));
expect(callback).not.toHaveBeenCalledWith(jasmine.stringMatching(/^bar$/));
});
});
});
/*
* 不规则匹配(自定义匹配):asymmetricMatch
* 某些场景下,我们希望能按照自己设计的规则进行匹配,此时我们可以自定义一个对象,该对象只要包含一个名为asymmetricMatch的方法即可。
* */
describe("custom asymmetry", function() {
var tester = {
asymmetricMatch: function(actual) {
var secondValue = actual.split(",")[1];
return secondValue === "bar";
}
};
it("dives in deep", function() {
expect("foo,bar,baz,quux").toEqual(tester);
});
describe("when used with a spy", function() {
it("is useful for comparing arguments", function() {
var callback = jasmine.createSpy("callback");
callback("foo,bar,baz");
expect(callback).toHaveBeenCalledWith(tester);
});
});
});
/*
* jasmine.clock()用来模拟操纵时间。
* 要想使用jasmine.clock(),先调用jasmine.clock().install告诉Jasmine你想要在spec或者suite操作时间,当你不需要使用时,务必调用jasmine.clock().uninstall来恢复时间状态。
*
* 示例中使用jasmine.clock().tick(milliseconds)来控制时间前进,本例中出现了三种时间控制方式:
* setTimeout: 定期执行一次,当jasmine.clock().tick()的时间超过了timeout设置的时间时触发
* setInterval: 定期循环执行,每当jasmine.clock().tick()的时间超过了timeout设置的时间时触发
* mockDate: 模拟一个指定日期(当不提供基准时间参数时,以当前时间为基准时间)
* */
describe("Manually ticking the Jasmine Clock", function() {
var timerCallback;
beforeEach(function() {
timerCallback = jasmine.createSpy("timerCallback");
jasmine.clock().install();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it("causes a timeout to be called synchronously", function() {
setTimeout(function() {
timerCallback();
}, 100);
expect(timerCallback).not.toHaveBeenCalled();
jasmine.clock().tick(101);
expect(timerCallback).toHaveBeenCalled();
});
it("causes an interval to be called synchronously", function() {
setInterval(function() {
timerCallback();
}, 100);
expect(timerCallback).not.toHaveBeenCalled();
jasmine.clock().tick(101);
expect(timerCallback.calls.count()).toEqual(1);
jasmine.clock().tick(50);
expect(timerCallback.calls.count()).toEqual(1);
jasmine.clock().tick(50);
expect(timerCallback.calls.count()).toEqual(2);
});
describe("Mocking the Date object", function(){
it("mocks the Date object and sets it to a given time", function() {
var baseTime = new Date();
jasmine.clock().mockDate(baseTime);
jasmine.clock().tick(50);
expect(new Date().getTime()).toEqual(baseTime.getTime() + 50);
});
});
});
/*
* Jasmine可以支持spec中执行异步操作。
* 当调用beforeEach, it和afterEach时,函数可以包含一个可选参数done,当spec执行完毕之后,调用done通知Jasmine异步操作已执行完毕。
*
* 另外补充一点,如果需要设置全局的默认超时时间,可以设置jasmine.DEFAULT_TIMEOUT_INTERVAL的值,
* 当异步执行时间超过设置的执行超时时间js将会报错。
* */
describe("Asynchronous specs", function() {
var value;
beforeEach(function(done) {
setTimeout(function() {
value = 0;
done();
}, 1);
});
// 在上面beforeEach的done()被执行之前,这个测试用例不会被执行
it("should support async execution of test preparation and expectations", function(done) {
value++;
expect(value).toBeGreaterThan(0);
done(); // 执行完done()之后,该测试用例真正执行完成
});
// Jasmine异步执行超时时间默认为5秒,超过后将报错
describe("long asynchronous specs", function() {
var originalTimeout;
beforeEach(function() {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
// 设置全局的默认超时时间
jasmine.DEFAULT_TIMEOUT_INTERVAL = 6000;
});
it("takes a long time", function(done) {
setTimeout(function() {
done();
}, 4000);
});
// 如果要调整指定用例的默认的超时时间,可以在beforeEach,it和afterEach中传入一个时间参数
//it("takes a long time for this spec", function(done) {
// setTimeout(function() {
// done();
// }, 6000);
//}, 7000);
afterEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
});
});
}());
image