操作系统

当前位置:金沙棋牌 > 操作系统 > TDD并不是看上去的那么美金沙棋牌:,使用教程

TDD并不是看上去的那么美金沙棋牌:,使用教程

来源:http://www.logblo.com 作者:金沙棋牌 时间:2019-11-09 19:48
  1. TDD通过边测验边编写代码,然后重构来防止重构所引起的谬误
  2. 因此自动化测量试验和不断集成工具,随即保持能够公布
  3. TDD第一步:

     1. 需求分解
     2. 将需求转化成测试
     3. 写一个失败的测试
     4. 逐步通过测试,再写一个测试
     5. 开始消除重复代码 (由于这个时候有测试在了,所以不用担心更改会引起集成错误)
    

    ### 见到这里感觉在境内集团曾经很难落到实处那几个了,因为时间很难让你去做这么些业务

  4. 相互测验,并不表明结果的准确性,而是印证代码与其搭档对象的并行行为的没有错

  5. 重构代码的时候绝不直接用调试器调节和测量检验,而是要把代码分为叁个凶横地软件开辟活动

    1. 分明更动点
    2. 鲜明测试点
    3. 蒙面测试点
    4. 改进代码
    5. 重构代码

    ### 先解析程序再写测验再重构,在此以前都搞反了,先重构再写测量试验所以很难去保障重构后代码正确,因为思维方向就狼狈,先重构再测量检验时固守本人的思路来写测量检验,更赞成于为了通过测量试验而写测验,而 先测量检验再重构思路更赞成于依靠业务来拓宽测量试验

  6. 数据库测验,增量式DDL脚本。一次只增添贰个列或许一张表,每一种步骤都得以回滚

  7. 数据库测量试验使用脚本大概其余格局增添进数据,然后开展测量检验

前言

随着前端开发的复杂度雨后春笋,前端工程师写出来的JS代码变得非常粗大,进而使项目变的交汇,维护的本金及难度不断加大,同盟迭代开采已经很难保障代码的成效性不被毁坏。所以在这里种情状下,思虑引进风流倜傥款前端的单元测量试验框架,来为代码避风挡雨是风流倜傥件很要紧的职业。

出处:

什么是单元测量试验

唯恐我们在看有个别付加物书籍的时候,当介绍三个类型时,会不禁提及测量试验。 譬如,单元测量检验,函数测量试验,或是TDD,BDD等测验情势。

那到底如何是单元测量试验呢?
单元测验,单元,测量检验。

哪些是单元(unit卡塔尔国?单元正是相对独立的成效模块。八个全体的、模块化的次序,都以由众多单元构成,单元达成本身的职分、然后与其他单元举行交互作用,最后协同实现全体程序效能。

什么样是测量检验?测量检验正是测验,推断测量试验指标对于有个别特定的输入有未有预料的出口。

据此怎么是单元测量检验?正是对构成程序的各种单元举行测量检验(大雾卡塔 尔(英语:State of Qatar)。工程上的三个共鸣是,假若程序的各类模块都以无可争辩的、模块与模块的总是是没有错的、那么程序基本上就能够是不错的。

由此单元测验便是那般三个定义,豆蔻梢头种努力确认保障构成程序的各种模块的精确性,进而确定保证整个程序的正确的方法论。

不问可以知道,单元测试的指标正是来确认保证你写的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

金沙棋牌 1

image

手动展开Chrome,输入localhost:9876

金沙棋牌 2

image

5、初始化 karma

karma init

金沙棋牌 3

image

说明:

  1. 测验框架:大家自然选jasmine

  2. 是还是不是丰裕 Require.js 插件

  3. 选用浏览器: 大家选Chrome

  4. 测验文件路线设置,文件能够动用通配符相称,例如*.js相称钦点目录下具有的js文件(实操中发掘该路径是 karma.conf.js 文件的相对路线,详见上面作者付诸的其实地度量试配置及表明卡塔 尔(英语:State of Qatar)

  5. 在测量试验文件路线下,需求免去的文件

  6. 是还是不是同意 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,岂不正是Waterfall中的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了。
  • 关怀测量检验并非两全。那大概是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卡塔 尔(英语:State of Qatar)其余,作者不知底我们怎么编程,作者平常的做法是从大局思索一下各类实用的贯彻方案,对于一些困难需要实际地去编制程序试试,最终权衡相比较,筛选一个最棒的方案去贯彻。而再三发急着去落实某生机勃勃效能,平时在会促成的是返工,而背后的重构基本上因为中期思索不足和成为了重写。所以,在实际操作进度中,你会开采,非常多时候的重构平日意味器重写,因为这么些”非成效性”的须求,你一定要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并从未想像中的那么粗略。和Waterfall形似,沃特erfall的每八个环节都依赖于前方那些环节的不易,即使大家平昔不科学的敞亮要求,那么对于TDD,Test Case和大家的Code都会的错的。所以,TDD中,Test Case是支付中最重视的环节,Test Case的成色的标题会一贯促成软件开辟的科学和频率。而TW的咨询师和Agile的Fans们如同天生就以为,TDD比Waterfall更能确切地打听须求。假设真是如此,用TDD举行需要剖判,前边一贯Waterfall就OK了

其余,某个Test Case并不一定那么好写,你或许十分八的编制程序时间要求花在有个别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 实例,并运营测量检验用例:

金沙棋牌 4

image

万生机勃勃大家点击图中的 debug 按键,步入 debug.html 并按 F12 张开开荒者工具,选择 Console 窗口,大家将能收看 jasmine 的推行日志

金沙棋牌 5

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 还生成了大器晚成层子文件夹,对应于实施测量试验的浏览器+版本号+操作系统版本卡塔 尔(英语:State of Qatar)

金沙棋牌 6

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;
            });
        });
    });
}());

金沙棋牌 7

image

本文由金沙棋牌发布于操作系统,转载请注明出处:TDD并不是看上去的那么美金沙棋牌:,使用教程

关键词: