用Jasmine和Sinon测试Backbone应用 (译)

最近在慢慢深入Backbone,也试着写一些测试,找一些合适的文档来学习。于是就找到了一个系列的文章 : Testing Backbone applications with Jasmine and Sinon – Part 1

概览

这是第一次展示如何测试Backbone.js应用的一系列文章在这里我们使用Jasmine BDD测试框架以及Sinon.JS库的spying,stubbingt和mocking。

在这一部分,我们将简单地说一下Backbone,紧接着我们将介绍Jasmine和Sinon的一些细节。在这个过程中,我们将会看到这些工具是如何完美地测试Backbone应用。

无处不在的Backbone

最近的几个月里,随着大量教程的推出以及一些高性能的实现,使得Backbone获得了更多的关注。

我们可以用下面的原因来解释Backbone的流行。它提供了一个最小化的model-view-controller(模式-视图-控制器)的框架来帮助开发者控制复杂的代码,但是与此同时它可以与其他一些框架一起工作。这就意味着,与Cappuccino这些富JS UI框架相比,Backbone不提供UI框架或者主题,但是它可以让开发者去选择DOM库。Backbone对于jQuery或者Zepto支持得特别好,但是你也可以使用别的库来代替他们。

Backbone MVC框架本身可以很好地支持自下而上的单元测试。models,collections,views和routers的隔离,意味着每个部分的行为可以单独地测试,使调试更简单。

对于那些有MVC框架(如Rails和Django)开发测试经验的人来说,这部分的开发也会很熟悉。很有多成熟的单元测试库,工具和方法可以测试这些应用。你只需要去写Javascript测试去保证你的前端代码和你的服务端代码同样的高效。

Jasmine BDD

(这里就不引用原文对于Jasmine的介绍了,简单地来说就是下面这样子)

  • 不依赖于其它任何框架;
  • 不需要DOM支持(也就是说可以脱离浏览器!);
  • 行为驱动式语法,写起来非常简单;
  • 支持对异步调用的测试;
  • 可以在多种环境中运行(包括Ruby、NodeJS、Scala、Java、BPM、.net、Perl)

Specs

多说无益,让我们来看看一个简单地用于Backbone Model的Jasmine测试。

it("should expose an attribute",function() {
  var episode = new Backbone.Model({
    title: "Hollywood - Part 2"
  });
  expect(episode.get("title"))
    .toEqual("Hollywood - Part 2");
});

一个spec只是简单地描述了一下预期的行为,上面的代码会执行该行为,以及一种或多种期望测试行为的代码

个别spec应短而且只测试行为的一个方面。如果你发现自己写了一些不同的期望或spec变得非常长,那么考虑将它变成其他spec。将你的spec与suites分组,并使用set up和teardown函数可以帮助这一点。

Suites

Specs分组为Suites,这被定义在describe()函数。例如,所有的specs为集模式可以被分成一组,如下所示:

describe("Episode model",function() {
  it("should expose an attribute",function() {
  ...
  });
  it("should validate on save",function() {
  ...
  });
});

很不错的一点是,Suites也可以嵌套。当你有很多specs的,因为你可以将它们组织成组离散块。我喜欢用一个描述块包与特定语境出发specs。这更好地保留了specs的会话风格。例如:

describe("Episode model",function() {
  describe("when creating a new episode",function() {
    it("should expose the title attribute",function() {
      ...
    });
    it("should have a default parental rating",function() {
      ...
    });
  });
});

beforeEach() and afterEach()

作为传统风格的xUnit测试框架,可以选择指定的代码在每次测试后运行。这是一个很不错的功能,以确保一致的条件下对每个试验,并用于设置变量和对象中的规格使用。

下面的示例使用beforeEach()来创建一个用于每个sepc的Model实例。

describe("Episode",function() {

  beforeEach(function() {
    this.episode = new Backbone.Model({
      title: "Hollywood - Part 2"
    });
  });

  it("should expose an attribute",function() {
    expect(this.episode.get("title"))
      .toEqual("Hollywood - Part 2");
  });

  it("should validate on save",function() {
    ...
  });

});

你可以提供一个beforeEach()和afterEach()方法在每个嵌套的describe,接着在你的specs,让你拥有专为每个Suites的规格一般或者特殊setup()和teardown()。正如您将在本文的其它部分看,这是非常方便的确实减少重复和控制每个specs的确切条件。

The spec runner

这种结构使得specs对于其他开发者能够直易读和容易理解的。,主要是因为对每个specs的说明以及期望的匹配的格式。

Jasmine还提供了一个简单的spec runner,这简直是与将运行所有你所提供的规范的脚本的HTML页面。下面显示一个suite的specs与单一spec故障的输出

我们将推出茉莉花其他一些有用的功能在本文的其它部分,因为我们需要他们,包括创建固定装置,使用jQuery和创建自己的自定义期望的匹配。现在,到Sinon.JS。

我们将在本文的其他部分说明一些Jasmine有用的功能,因为我们需要这些功能包括creating fixtures,使用jQuery和创建自己的自定义期望的匹配。现在,让我们试试Sinon.JS。

Sinon.JS

Sinon.js提供了fake对象——spies,stubs和mocks,是一个专用于Javascript的个测试辅助工具。利用这些结构在测试JavaScript代码是不是已经真正流行起来,只是刚刚开始。但是,如果你正在开发一个丰富的,复杂的应用程序,如您在使用Backbone,然后fake对象是测试工具集的一个非常有用的部分。

Christian Johansen——Sinon.JS的创建者,解释了为什么你需要fake。在Javascript中,这些原因可以分为

  • 性能 真实的DOM操作,依靠定时行为及网络活动减慢了测试
  • 隔离 单元测试应把重点放在小的一块功能成为可能,并解耦不可靠的或低依赖

使用fake对象是拥抱TDD和BDD的基本组成部分。他们基本上是让代码能够脱离其依赖进行测试。任何API或模块测试你的代码依赖于可以fake的,你需要为你的测试的方式作出回应。您也可以检查fake的方法,看看测试的过程中,究竟他们是如何被调用

Sinon.JS允许你提供fake几乎所有的东西。您可以将自己的应用程序的fake,在jQuery的,当XMLHttpRequest API本身特定的行为,或者你甚至可以fake JavaScript的计时方法,对于有时间依赖性,如动画和超时快速的测试代码

Sinon.JS提供了三种类型的fake对象:spies,stubs and mocks。

Spies

(ps:这部分翻译得很烂)

Spies是跟踪的方式和他们被调用函数,以及返回想要的值。这是异步的,这在事件驱动的应用非常有用,你可以发送一个spy以记录正在发生的事情,即使这些方法都是匿名或封闭。

spies可以“匿名”,也可以对现有功能的spy。

一个匿名的spy只是用spying功能的空函数,可以记录下它是如何被使用的。像一个真正的speis 被送往敌后与连接到它的胸口的麦克风,而测试方法是不知道。这里是一个spy测试一个简单的Backbone自定义事件绑定的例子:

it("should fire a callback when 'foo' is triggered",function() {
  // Create an anonymous spy
  var spy = sinon.spy();

  // Create a new Backbone 'Episode' model
  var episode = new Episode({
    title: "Hollywood - Part 2"
  });

  // Call the anonymous spy method when 'foo' is triggered
  episode.bind('foo',spy); 

  // Trigger the foo event
  episode.trigger('foo'); 

  // Expect that the spy was called at least once
  expect(spy.called).toBeTruthy(); 
});

这测试将会通过,当spies被调用一次或多次,不管它是如何调用或什么参数。然而,Sinon提供了一些方法,让你严格的限制调用次数,的确是每次调用看起来都像,spy返回了什么。

Spying行为也可以被连接到一个现有的方法。欢快的,我喜欢叫这些“moles”。这是有用的,以检查某些小部分的功能调用代码的另一部分与预期一样。例如,您可能要检查模型的保存方法,确保正确的jQuery $.ajax 调用

it("should make the correct server request",function() {

  var episode = new Backbone.Model({
    title: "Hollywood - Part 2",url: "/episodes/1"
  });

  // Spy on jQuery's ajax method
  var spy = sinon.spy(jQuery,'ajax');

  // Save the model
  episode.save();

  // Spy was called
  expect(spy).toHaveBeenCalled();
  // Check url property of first argument
  expect(spy.getCall(0).args[0].url)
    .toEqual("/episodes/1");

  // Restore jQuery.ajax to normal
  jQuery.ajax.restore();
});

Stubs and Mocks

保留原文关于Stubs和Mocks的解释

Stubs and mocks in Sinon implement all the features of spies,but with some added features. Stubs allow you to replace the existing behavIoUr of a particular method with whatever you like. This is great for emulating exceptions and error scenarios from external dependencies so you can test that your code will respond appropriately. It also allows you to start development when other dependencies are not yet in place.

Mocks provide all this,but instead mock an entire API and set built-in expectations on how they will be utilised. Like spies they track how they have been used,and like stubs they respond in a pre-programmed manner according to the needs of the test. However,unlike a spy,the expectations for their behavIoUr is pre-programmed,and a single verification step at the end will fail if any of these individual expectations are not met.

简单的来说

Stubs就是返回你想要的结果。
Mocks就是确保方法调用

  • Stub是代码的一部分。其目的就是用简单的行为替换复杂的行为,从而允许独立地测试代码的一部分。
  • Mock Object是使用来代替与你的代码协作的对象的对象,这样代码可以调用Mock Object的方法,这些方法调用的结果是由你的测试设置好的。

相关文章: mock stub与tdd

Fake Ajax and fake servers

Sinon不局限于Spying on和stubbing普通函数方法。它还提供了快捷的伪造Ajax响应方式。这意味着您可以从您的JSON数据源完全隔离测试你的代码,并且不依赖于以运行spec suites运行的Web应用程序。此外,你可以测试你的应用程序响应适当的时候将其从幸福的路径,包括无效的JSON和各种HTTP响应代码偏离。

这里有用于Backbone模型spec的获取方法,它使用一个fake的server来响应Ajax请求的一个简单的例子:

describe("Episode model",function() {
  beforeEach(function() {
    this.server = sinon.fakeServer.create();
  });

  afterEach(function() {
    this.server.restore();
  });

  it("should fire the change event",function() {
    var callback = sinon.spy();

    // Set how the fake server will respond
    // This reads: a GET request for /episode/123 
    // will return a 200 response of type 
    // application/json with the given JSON response body
    this.server.respondWith("GET","/episode/123",[200,{"Content-Type": "application/json"},'{"id":123,"title":"Hollywood - Part 2"}']);

    var episode = new Episode({id: 123});

    // Bind to the change event on the model
    episode.bind('change',callback);

    // makes an ajax request to the server
    episode.fetch(); 

    // Fake server responds to the request
    this.server.respond(); 

    // Expect that the spy was called with the new model
    expect(callback.called).toBeTruthy();
    expect(callback.getCall(0).args[0].attributes)
      .toEqual({
        id: 123,title: "Hollywood - Part 2"
      });

  });

});

这个spec测试可以通过下面这个简单的Backbone Model

var Episode = Backbone.Model.extend({
  url: function() {
    return "/episode/" + this.id;
  }
});

还有更多的兴农,我们这里不讨论。特别是,fake timer是为测试时间依赖的功能非常有用,例如动画,而不会减慢您的测试。

总结

(ps:总结,就是翻译得一般般)

在Backbone应用的带血的世界里,复杂的异步和相互依存的行为可能会导致任何开发人员伤透脑筋。Backbone可以帮助开发人员构建自己的代码转换成小的,独立的models,collections,views和routers。但是,这是真的只是成功的一半。如果没有经过充分测试的代码将会有更多的未被发现的缺陷,而那些被发现将很难追查。其他团队成员可能会无意中破坏你的代码,或者干脆误解了它的目的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图:提供一种方法顺序访问一个聚合对象中的每个元素,而又不想暴露该对象的内部表示。应用:STL标准库迭代器实现、Java集合类型迭代器等模式结构:心得:迭代器模式的目的是在不获知集合对象内部细节的同时能对集合元素进行遍历操作
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:(1)同步阻塞IO(BlockingIO):即传统的IO模型。(2)同步非阻塞IO(Non-blockingIO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的N
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定义一系列算法,把他们封装起来,并且使他们可以相互替换,使算法可以独立于使用它的客户而变化。应用:排序的比较方法、封装针对类的不同的算法、消除条件判断、寄存器分配算法等。模式结构:心得:对对象(Context)的处理操作可
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个作用于某对象结构中的各元素的操作,它使你在不改变各元素的类的前提下定义作用于这些元素的新操作。应用:作用于编译器语法树的语义分析算法。模式结构:心得:访问者模式是要解决对对象添加新的操作和功能时候,如何尽可能不修改对象的类的一种方
命令模式(Command)命令模式(Command)[Action/Transaction]意图:将一个请求封装为一个对象,从而可用不同的请求对客户参数化。对请求排队或记录请求日志,以及支持可撤消的操作。应用:用户操作日志、撤销恢复操作。模式结构:心得:命令对象的抽象接口(Command)提供的两个
生成器模式(Builder)生成器模式(Builder)意图:将一个对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。 应用:编译器词法分析器指导生成抽象语法树、构造迷宫等。模式结构:心得:和工厂模式不同的是,Builder模式需要详细的指导产品的生产。指导者(Director)使用C
设计模式学习心得《设计模式:可复用面向对象软件的基础》一书以更贴近读者思维的角度描述了GOF的23个设计模式。按照书中介绍的每个设计模式的内容,结合网上搜集的资料,我将对设计模式的学习心得总结出来。网络上关于设计模式的资料和文章汗牛充栋,有些文章对设计模式介绍生动形象。但是我相信“一千个读者,一千个
工厂方法模式(Factory Method)工厂方法模式(Factory Method)[Virtual Constructor]意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实力化延迟到子类。应用:多文档应用管理不同类型的文档。模式结构:心得:面对同一继承体系(Produc
单例模式(Singleton)单例模式(Singleton)意图:保证一个类只有一个实例,并提供一个访问它的全局访问点。应用:Session或者控件的唯一示例等。模式结构:心得:单例模式应该是设计模式中最简单的结构了,它的目的很简单,就是保证自身的实例只有一份。实现这种目的的方式有很多,在Java中
装饰者模式(Decorator)装饰者模式(Decorator)[Wrapper]意图:动态的给一个对象添加一些额外的职责,就增加功能来说,比生成子类更为灵活。应用:给GUI组件添加功能等。模式结构:心得:装饰器(Decorator)和被装饰的对象(ConcreteComponent)拥有统一的接口
抽象工厂模式(Abstract Factory)抽象工厂模式(Abstract Factory)[Kit]意图:提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。应用:用户界面工具包。模式结构:心得:工厂方法把生产产品的方式封装起来了,但是一个工厂只能生产一类对象,当一个工厂需要生
桥接模式(Bridge)桥接模式(Bridge)[Handle/Body]意图:将抽象部分与它的实现部分分离,使他们都可以独立的变化。应用:不同系统平台的Windows界面。模式结构:心得:用户所见类体系结构(Window派生)提供了一系列用户的高层操作的接口,但是这些接口的实现是基于具体的底层实现
适配器模式(Adapter)适配器模式(Adapter)[Wrapper]意图:将类的一个接口转换成用户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。应用:将图形类接口适配到用户界面组件类中。模式结构:心得:适配器模式一般应用在具有相似接口可复用的条件下。目标接口(Targ
组合模式(Composition)组合模式(Composition)意图:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。应用:组合图形、文件目录、GUI容器等。模式结构:心得: 用户(Client)通过抽象类(Component)提供的公用接口统一
原型模式(Prototype)原型模式(Prototype)意图:用原型实例制定创建对象的种类,并且通过拷贝这些原型创建新的对象。应用:Java/C#中的Clonable和IClonable接口等。模式结构:心得:原型模式本质上就是对象的拷贝,使用对象拷贝代替对象创建的原因有很多。比如对象的初始化构
什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。