web学习笔记01-Jasmine-Unit-Test

Unit Test

  • 单元测试概念(Unit Testing)

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

  • 单元测试必要性

        随着项目规模的增加,函数、方法、变量都在递增,维护的难度不断加大,以及测试提出的各种bug导致修改代码的时候会将原本整洁的代码变得混乱。
        经常出现同一个接口以不同的名称出现在不同的控制器中,这个时候往往会去重构代码,但是重构代码的时候没人会保证自己将万无一失,重构的代码还是正确的,方法一样跑通等等。这个时候就需要单元测试了,单元测试是一个衡量标准,告诉开发人员这么做是否将改变结果。保证重构后的代码的兼容性,减少人力测试的过程,降低维护成本。

Jasmine

    Jasmine是一个behavior-driven development ( 行为驱动开发 ) 测试框架, 不依赖于任何其他JavaScript框架, 不依赖DOM, 并且有很简洁的语法让你能够很轻松的编写单元测试。它既可以在html文件中运行,也可以和jsTestDriver整合,在jsTestDriver中运行。

  • BDD 行为驱动开发,是一种新的敏捷开发方法。相对于TDD(测试驱动开发),它更趋向于需求,需要共同利益者的参与,强调用户故事和行为;是面向开发者、QA、非技术人员或商业参与者共同参与和理解的开发活动,而不是TDD简单地只关注开发者的方法论;

  • TDD测试驱动开发,是一种不同于传统软件开发流程:开发结束再测试介入的新型开发方法。要求把项目按功能点划分,在编写每个功能点前先编写测试代码,然后再编写使测试通过的功能代码,通过测试来推动整个开发工作。

搭建环境

1.下载源文件

jasmine源文件下载地址

图1.png

    下载jasmine-standlone-2.5.0.zip即可。这是一个范例,但是可以直接使用。运行起来如下图显示:
运行图

2.使用

    将下载下来的文件夹中lib文件夹下的jasmine-2.5.0文件夹直接拖入你所需要用的项目。在index.html 中引入下面几句

  <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.5.0/jasmine_favicon.png">
  <link rel="stylesheet" href="lib/jasmine-2.5.0/jasmine.css">

  <script src="lib/jasmine-2.5.0/jasmine.js"></script>
  <script src="lib/jasmine-2.5.0/jasmine-html.js"></script>
  <script src="lib/jasmine-2.5.0/boot.js"></script>

    之后便可以直接创建对应的测试用例js文件了。

jasmine基础语法

一个简单的例子

describe("A suite", function() {  
    it("contains spec with an expectation", function() {  
        expect(true).toBe(true);  
    });  
}); 

1.两个核心方法

  • describe方法

        describe是jasmine用于描述测试集(Test Suite)的全局函数,作为测试集的开始,一般有两个参数,字符串和方法。字符串作为特定用例组的名字和标题。方法是包含实现用例组的代码。一个测试集合可以包含多个spec(测试点)。

  • it方法

        jasmine中用方法it来开始specs。it方法和describe方法类似, 同样有两个参数,一个String,一个function;String用来描述测试点(spec),function是具体的测试代码。

示例代码

describe("This is an exmaple suite", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
    expect(false).toBe(false);
    expect(false).not.toBe(true);
  });
});

2.四个核心概念

Jasmine四个核心概念:
  • 分组(Suites)
  • 用例(Specs)
  • 期望(Expectations)
  • 匹配(Matchers)

    • 分组
      (Suites)
          Suites可以理解为一组测试用例,以函数describe(string,function)封装,describe函数接受两个参数,一个字符串和一个函数。字符串是这个Suites的名字或标题(通常描述下测试内容),函数是实现Suites的代码块。一个Suite可以包含多个Specs,一个Specs可以包括多个expect

    • 用例
      用例(Specs)
          Specs可以理解为一个测试用例,使用全局的Jasmin函数it创建。和describe一样接受两个参数,一个字符串和一个函数,函数就是要执行的测试代码,字符串就是测试用例的名字。一个Spec可以包含多个expectations来测试代码。

    • 期望
      (Expectations)
          Expectations由expect 函数创建。接受一个参数。和Matcher一起联用,设置测试的预期值。返回ture或false。
          在分组(describe)中可以写多个测试用例(it),也可以再进行分组(describe),在测试用例(it)中定义期望表达式(expect)和匹配判断(toBe)。看一个简单的Demo:

describe("A suite", function() {//suites
    var a;
    it("A spec", function() {//spec
      a = true;
      expect(a).toBe(true);//expectations
    });

    describe("a suite", function() {//inner suites
           it("a spec", function() {//spec
           expect(a).toBe(true);//expectations
        });
  });
});
  • 匹配
    (Matchers)
        Matcher实现断言的比较操作,一个“期望值”与“实际值”的对比,如果结果为true,则通过测试,反之,则失败。每一个matcher都能通过not执行否定判断。
expect(a).toBe(true);//期望变量a为true
expect(a).toEqual(true);//期望变量a等于true
expect(a).toMatch(/reg/);//期望变量a匹配reg正则表达式,也可以是字符串
expect(a.foo).toBeDefined();//期望a.foo已定义
expect(a.foo).toBeUndefined();//期望a.foo未定义
expect(a).toBeNull();//期望变量a为null
expect(a.isMale).toBeTruthy();//期望a.isMale为真
expect(a.isMale).toBeFalsy();//期望a.isMale为假
expect(true).toEqual(true);//期望true等于true
expect(a).toBeLessThan(b);//期望a小于b
expect(a).toBeGreaterThan(b);//期望a大于b
expect(a).toThrowError(/reg/);//期望a方法抛出异常,异常信息可以是字符串、正则表达式、错误类型以及错误类型和错误信息
expect(a).toThrow();//期望a方法抛出异常
expect(a).toContain(b);//期望a(数组或者对象)包含b

    自定义Matcher(被称为Matcher Factories)实质上是一个函数(该函数的参数可以为空),该函数返回一个闭包,该闭包的本质是一个compare函数,compare函数接受2个参数:actual value 和 expected value。
    compare函数必须返回一个带pass属性的结果Object,pass属性是一个Boolean值,表示该Matcher的结果(为true表示该Matcher实际值与预期值匹配,为false表示不匹配),也就是说,实际值与预期值具体的比较操作的结果,存放于pass属性中。

其他matchers:

jasmine.any(Class)–传入构造函数或者类返回数据类型作为期望值,返回true表示实际值和期望值数据类型相同:

it("matches any value", function() {
    expect({}).toEqual(jasmine.any(Object));
    expect(12).toEqual(jasmine.any(Number));
});

jasmine.anything()–如果实际值不是null或者undefined则返回true:

it("matches anything", function() {
    expect(1).toEqual(jasmine.anything());
});

jasmine.objectContaining({key:value})–实际数组只要匹配到有包含的数值就算匹配通过:

foo = {
      a: 1,
      b: 2,
      bar: "baz"
};
expect(foo).toEqual(jasmine.objectContaining({bar: "baz"}));

jasmine.arrayContaining([val1,val2,…])–stringContaining可以匹配字符串的一部分也可以匹配对象内的字符串:

expect({foo: 'bar'}).toEqual({foo: jasmine.stringMatching(/^bar$/)});
expect('foobarbaz').toEqual({foo: jasmine.stringMatching('bar')});

3.Setup和Teardown方法

    为了减少重复性的代码,jasmine提供了beforeEach、afterEach、beforeAll、afterAll方法。

  • beforeEach() :在describe函数中每个Spec执行之前执行;
  • afterEach() :在describe函数中每个Spec执行之后执行;
  • beforeAll() :在describe函数中所有的Specs执行之前执行,且只执行一次
  • afterAll () : 在describe函数中所有的Specs执行之后执行,且只执行一次
结果如图所示:

结果

4.describe函数的嵌套

    每个嵌套的describe函数,都可以有自己的beforeEachafterEach函数。
    在执行每个内层Spec时,都会按嵌套的由外及内的顺序执行每个beforeEach函数,所以内层Sepc可以访问到外层Sepc中的beforeEach中的数据。类似的,当内层Spec执行完成后,会按由内及外的顺序执行每个afterEach函数。

describe("A spec", function() {
  var foo;

  beforeEach(function() {
    foo = 0;
    foo += 1;
  });

  afterEach(function() {
    foo = 0;
  });

  it("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });

  it("can have more than one expectation", function() {
    expect(foo).toEqual(1);
    expect(true).toEqual(true);
  });

  describe("nested inside a second describe", function() {
    var bar;
    beforeEach(function() {
      bar = 1;
    });
    it("can reference both scopes as needed", function() {
      expect(foo).toEqual(bar);
    });
  });
});

5.禁用Suites,挂起Specs

    Suites可以被Disabled。在describe函数名之前添加x即可将Suite禁用。
    被DisabledSuites在执行中会被跳过,该Suite的结果也不会显示在结果集中。

xdescribe("A spec", function() {
  var foo;

  beforeEach(function() {
    foo = 0;
    foo += 1;
  });

  it("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });
});

    有3种方法可以将一个Spec标记为Pending。被Pending的Spec不会被执行,但是Spec的名字会在结果集中显示,只是标记为Pending。

  • 如果在Spec函数it的函数名之前添加x(xit),那么该Spec就会被标记为Pending。
  • 一个没有定义函数体的Sepc也会在结果集中被标记为Pending。
  • 如果在Spec的函数体中调用pending()函数,那么该Spec也会被标记为Pending。pending()函数接受一个字符串参数,该参数会在结果集中显示在PENDING WITH MESSAGE:之后,作为为何被Pending的原因。
describe("Pending specs", function() {

  xit("can be declared 'xit'", function() {
    expect(true).toBe(false);
  });

  it("can be declared with 'it' but without a function");

  it("can be declared by calling 'pending' in the spec body", function() {
    expect(true).toBe(false);
    pending('this is why it is pending');
  });
});

6.Spy追踪

    Jasmine具有函数的追踪和反追踪的双重功能,这东西就是Spy。Spy能够存储任何函数调用记录和传入的参数,Spy只存在于describe和it中,在spec执行完之后销毁。
describe("A spy", function() {
  var foo, bar = null;
  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      }
    };
    spyOn(foo, 'setBar');//给foo对象的setBar函数绑定追踪
    foo.setBar(123);
    foo.setBar(456, 'another param');
  });
  it("tracks that the spy was called", function() {
    expect(foo.setBar).toHaveBeenCalled();//toHaveBeenCalled用来匹配测试函数是否被调用过
  });
  it("tracks all the arguments of its calls", function() {
    expect(foo.setBar).toHaveBeenCalledWith(123);//toHaveBeenCalledWith用来匹配测试函数被调用时的参数列表
    expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');//期望foo.setBar已经被调用过,且传入参数为[456, 'another param']
  });
  it("stops all execution on a function", function() {
    expect(bar).toBeNull();//用例没有执行foo.setBar,bar为null
  });
});
    and.callThrough–spy链式调用and.callThrough后,在获取spy的同时,调用实际的函数。
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();//调用and.callThrough方法
    foo.setBar(123);
    fetchedBar = foo.getBar();//因为and.callThrough,这里执行的是foo.getBar方法,而不是spy的方法
  });
  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(123);
  });
});
    and.returnValue–spy链式调用and.returnValue 后,任何时候调用该方法都只会返回指定的值,
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);//指定返回值为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);//默认返回指定的returnValue值
  });
});
    and.callFake–spy链式添加and.callFake相当于用新的方法替换spy的方法
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() {//指定callFake方法
      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);//执行callFake方法,返回1001
  });
});
    and.throwError–spy链式调用and.callError后,任何时候调用该方法都会抛出异常错误信息:
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("error");//指定throwError
  });
  it("throws the value", function() {
    expect(function() {
      foo.setBar(123)
    }).toThrowError("error");//抛出错误异常
  });
});
    and.stub–spy恢复到原始状态,不执行任何操作。直接看下代码:
describe("A spy", function() {
  var foo, bar = null;
  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      }
    };
    spyOn(foo, 'setBar').and.callThrough();
  });
  it("can call through and then stub in the same spec", function() {
    foo.setBar(123);
    expect(bar).toEqual(123);
    foo.setBar.and.stub();//把foo.setBar设置为原始状态,and.callThrough无效
    bar = null;
    foo.setBar(123);//执行赋值无效
    expect(bar).toBe(null);
  });
});
    Spy的其他方法
.calls.any():记录spy是否被访问过,如果没有,则返回false,否则,返回true;
.calls.count():记录spy被访问过的次数;
.calls.argsFor(index):返回指定索引的参数;
.calls.allArgs():返回所有函数调用的参数记录数组;
.calls.all ():返回所有函数调用的上下文、参数和返回值;
.calls.mostRecent():返回最近一次函数调用的上下文、参数和返回值;
.calls.first():返回第一次函数调用的上下文、参数和返回值;
.calls.reset():清除spy的所有调用记录;
参考:

官方文档
jasmine测试框架简介
JavaScript单元测试框架-Jasmine
JavaScript 单元测试框架:Jasmine 初探
web前端开发七武器—Jasmine入门教程(上)
前端测试-jasmine
开启JavaScript测试之路–Jasmine

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值