依照NodeJS的左右端分离的思想与实施,前后端分离了

内外端分离了,然后呢?

2015/07/09 · CSS,
HTML5,
JavaScript · 2
评论 ·
前后端

原文出处:
邱俊涛的博客(@正反反长)   

 

 

基于NodeJS的上下端分离的怀恋与实践(三)轻量级的接口配置建模框架,nodejs建模

前言

应用Node做上下端分离的支出格局带来了部分性质及支付流程上的优势,
但同时也面临诸多挑战。在Tmall复杂的政工及技术架构下,后端必须尊敬Java搭建基础架构,同时提供相关事情接口供前端选用。Node在全体环境中最要紧的做事之一就是代理那个事情接口,以福利前端(Node端和浏览器端)整合数据做页面渲染。怎样搞好代理工作,使得上下端支付分离之后,如故能够在工艺流程上无缝过渡,是大家须求考虑的题材。本文将就该难点做连锁探究,并提议解决方案。

亚洲必赢官网 1

鉴于后端提供的接口方式可能两种七种,同时开发人士在编制Node端代码访问这么些接口的艺术也有可能二种三种。如若我们在接口访问方式及应用上不做统一架构处理,则会带来以下一些难点:

1.
每一个开发人士使用各自的代码风格编写接口访问代码,造成工程目录及编码风格混乱,维护相对辛勤。
2.
每一个开发人士编写自己的mock数据格局,开发完成之后,必要手工修改代码移除mock。
3.
每一个开发人士为了促成接口的分化环境切换(平常,预发,线上),可能各自维护了有些布局文件。

  1. 数量接口调用方式不能被逐一业务model万分有利地复用。
    5.
    对于数据接口的叙述约定散落在代码的一一角落,有可能跟后端人士约定的接口文档不平等。
    6.
    整个项目分别开发从此,对于接口的联调或者测试回归费用如故很高,必要涉及到每一个接口提供者和使用者。
    于是大家期待有这么一个框架,通过该框架提供的编制去讲述工程项目中凭借的兼具外部接口,对她们开展联合保管,同时提供灵活的接口建模及调用格局,并且提供方便的线上环境和生产环境切换情势,使前后端支付无缝结合。ModelProxy就是满意如此要求的轻量级框架,它是Midway
    Framework 大旨部件之一,也得以独立选拔。使用ModelProxy可以牵动如下优点:

  2. 分歧的开发者对于接口访问代码编写格局统一,含义清晰,下落维护难度。
    2.
    框架之中使用工厂+单例方式,完成接口一遍配置多次复用。并且开发者可以任意定制组装自己的工作Model(依赖注入)。

  3. 可以非常方便地促成线上,平时,预发环境的切换。
  4. 嵌入river-mock和mockjs等mock引擎,提供mock数据充裕有利。
    5.
    应用接口配置文件,对接口的依靠描述做统一的管住,避免散落在逐个代码之中。
    6.
    援助浏览器端共享Model,浏览器端可以利用它做前端数据渲染。整个代理进程对浏览器透明。
    7.
    接口配置文件本身是结构化的叙述文档,能够使用river工具集合,自动生成文档。也可应用它做连锁自动化接口测试,使所有开发进程形成一个闭环。

ModelProxy工作原理图及连锁支出进度图览

亚洲必赢官网 2

在上图中,开发者首先须要将工程项目中享有看重的后端接口描述,根据指定的json格式,写入interface.json配置文件。要求时,要求对各样接口编写一个规则文件,也即图中interface
rules部分。该规则文件用于在开发阶段mock数据仍旧在联调阶段采纳River工具集去申明接口。规则文件的内容在于选用哪个种类mock引擎(比如
mockjs, river-mock
等等)。配置已毕未来,即可在代码中听从自己的需求创制自己的工作model。

上边是一个粗略的例子:

【例一】

首先步 在工程目录中开创接口配置文件interface.json,
并在其中添加主搜接口json定义

复制代码 代码如下:

{
    “title”: “padTaobao项目数目接口集合定义”,
    “version”: “1.0.0”,
    “engine”: “mockjs”,
    “rulebase”: “./interfaceRules/”,
    “status”: “online”,
    “interfaces”: [ {
        “name”: “主搜索接口”,
        “id”: “Search.getItems”,
        “urls”: {
            “online”: “”
        }
    } ]
}

其次步 在代码中开创并动用model

复制代码 代码如下:

// 引入模块
var ModelProxy = require( ‘modelproxy’ );

// 全局开头化引入接口配置文件  (注意:开始化工作有且唯有四回)
ModelProxy.init( ‘./interface.json’ );

// 创造model 越多创制格局请参后文
var searchModel = new ModelProxy( {
    searchItems: ‘Search.getItems’  // 自定义方法名:
配置文件中的定义的接口ID
} );

// 使用model, 注意: 调用艺术所急需的参数即为实际接口所急需的参数。
searchModel.searchItems( { q: ‘iphone6’ } )
    // !注意 必须调用 done
方法指定回调函数,来取得地点异步调用searchItems获得的数目!
    .done( function( data ) {
        console.log( data );
    } )
    .error( function( err ) {
        console.log( err );
    } );

ModelProxy的职能丰裕性在于它帮衬种种方式的profile以创办须要工作model:

使用接口ID创立>生成的目的会取ID最终’.’号前边的单词作为艺术名

复制代码 代码如下:

ModelProxy.create( ‘Search.getItem’ );

行使键值JSON对象>自定义方法名: 接口ID

复制代码 代码如下:

ModelProxy.create( {
    getName: ‘Session.getUserName’,
    getMyCarts: ‘Cart.getCarts’
} );

应用数组情势>取最终 . 号前边的单词作为艺术名
下例中变化的方法调用名依次为: Cart_getItem, getItem, suggest, getName

复制代码 代码如下:

ModelProxy.create( [ ‘Cart.getItem’, ‘Search.getItem’,
‘Search.suggest’, ‘Session.User.getName’ ] );

前缀形式>所有满足前缀的接口ID会被引入对象,并取其后半有的作为艺术名

复制代码 代码如下:

ModelProxy.create( ‘Search.*’ );

再者,使用那么些Model,你可以很轻易地促成统一请求或者看重请求,并做相关模板渲染

【例二】 合并请求

复制代码 代码如下:

var model = new ModelProxy( ‘Search.*’ );

// 合并请求 (上边调用的model方法除done之外,皆为布局接口id时指定)
model.suggest( { q: ‘女’ } )
    .list( { keyword: ‘iphone6’ } )
    .getNav( { key: ‘流行衣饰’ } )
    .done( function( data1, data2, data3 ) {
        // 参数顺序与方法调用顺序一致
        console.log( data1, data2, data3 );
    } );

【例三】 依赖请求

复制代码 代码如下:

var model = new ModelProxy( {
    getUser: ‘Session.getUser’,
    getMyOrderList: ‘Order.getOrder’
} );
// 先获得用户id,然后再依据id号拿到订单列表
model.getUser( { sid: ‘fdkaldjfgsakls0322yf8’ } )
    .done( function( data ) {
        var uid = data.uid;
        // 二次数据请求信赖首次得到的id号
        this.getMyOrderList( { id: uid } )
            .done( function( data ) {
                console.log( data );
            } );
    } );

别的ModelProxy不仅在Node端可以应用,也足以在浏览器端使用。只必要在页面中引入官方包提供的modelproxy-client.js即可。
【例四】浏览器端使用ModelProxy

复制代码 代码如下:

<!– 引入modelproxy模块,该模块本身是由KISSY封装的正式模块–>
<script src=”modelproxy-client.js” ></script>
<script type=”text/javascript”>
    KISSY.use( “modelproxy”, function( S, ModelProxy ) {
        // !配置基础路径,该路线与第二步中布署的掣肘路径一致!
        // 且全局配置有且只有两遍!
        ModelProxy.configBase( ‘/model/’ );

依照NodeJS的左右端分离的思想与实施,前后端分离了。        // 创建model
        var searchModel = ModelProxy.create( ‘Search.*’ );
        searchModel
            .list( { q: ‘ihpone6’ } )
            .list( { q: ‘冲锋衣’ } )
            .suggest( { q: ‘i’ } )
            .getNav( { q: ‘滑板’ } )
            .done( function( data1, data2, data3, data4 ) {
                console.log( {
                    “list_ihpone6”: data1,
                    “list_冲锋衣”: data2,
                    “suggest_i”: data3,
                    “getNav_滑板”: data4
                } );
            } );
    } );
</script>

同时,ModelProxy可以包容Midway另一为主零部件Midway-XTPL一起行使,完成数量和模板以及相关渲染进程在浏览器端和服务器端的全共享。关于ModelProxy的详尽教程及文档请移步

总结

ModelProxy以一种配置化的轻量级框架存在,提供自己的接口model组装及应用方法,同时很好的化解前后端支付情势分离中的接口使用标准难题。在全路项目开销进度中,接口始终只须求定义描述两遍,前端开发人士即可引用,同时利用River工具自动生成文档,形成与后端开发人士的契约,并做连锁自动化测试,极大地优化了全方位软件工程支出进度。

【注】River 是阿里公司研发的左右端统一接口规范及相关工具集合的统称

前言

上下端分离已经是业界所共识的一种开发/计划形式了。所谓的前后端分离,并不是传统行业中的按机关分割,一部分人纯做前端(HTML/CSS/JavaScript/Flex),另一片段人纯做后端,因为那种格局是不工作的:比如很多团体利用了后端的模版技术(JSP,
Free马克er,
ERB等等),前端的开发和调剂要求一个后台Web容器的协助,从而无法形成真正的诀别(更不要提在陈设的时候,由于动态内容和静态内容混在一块儿,当设计动态静态分流的时候,处理起来相当麻烦)。关于前后端支出的另一个座谈能够参照那里。

固然通过API来解耦前端和后端开发进度,前后端通过RESTFul的接口来通讯,前端的静态内容和后端的动态统计分别支付,分别安插,集成反之亦然是一个绕不开的题材

前端/后端的行使都得以单独的运作,不过集成起来却不做事。大家须求花费巨量的生命力来调节,直到上线前仍然没有人有信念有所的接口都是办事的。

 前言

 前言

  前后端分离已经是业界所共识的一种开发/安排情势了。所谓的前后端分离,并不是观念行业中的按单位分割,一部分人纯做前端(HTML/CSS/JavaScript/Flex),另一有些人纯做后端,因为那种办法是不办事的:比如很多团队选择了后端的沙盘技术(JSP,
FreeMarker,
ERB等等),前端的开销和调试须求一个后台Web容器的协助,从而不可能达成真正的分别(更毫不提在布署的时候,由于动态内容和静态内容混在一道,当设计动态静态分流的时候,处理起来越发费力)。关于前后端支付的另一个谈谈可以参照那里。

  即使通过API来解耦前端和后端开发进程,前后端通过RESTFul的接口来通讯,前端的静态内容和后端的动态计算分别支付,分别布置,集成依旧是一个绕不开的难点—
前端/后端的施用都可以单独的运行,不过集成起来却不工作。大家必要费用大批量的生气来调节,直到上线前仍然没有人有信心有所的接口都是工作的。

一点背景

一个超级的Web应用的布局看起来是如此的:

亚洲必赢官网 3

前后端都分别有温馨的费用流程,营造工具,测试集合等等。前后端仅仅经过接口来编程,这一个接口可能是JSON格式的RESTFul的接口,也恐怕是XML的,重点是后台只承担数据的提供和测算,而完全不处理显示。而前者则负责获得数量,社团数量并呈现的劳作。那样结构清晰,关心点分离,前后端会变得绝对独立并松耦合。

上述的场所如故比较可观,大家其实在事实上条件中会有分外复杂的场景,比如异构的互连网,异构的操作系统等等:

亚洲必赢官网 4

在其实的风貌中,后端可能还会更扑朔迷离,比如用C语言做多少搜集,然后通过Java整合到一个数据仓库,然后该数据仓库又有一层Web
Service,最终若干个那样的Web
Service又被一个Ruby的聚合Service整合在一块再次来到给前端。在如此一个扑朔迷离的连串中,后台任意端点的败北都可能阻塞前端的支出流程,由此我们会利用mock的艺术来缓解这些难题:

亚洲必赢官网 5

这么些mock服务器可以启动一个大致的HTTP服务器,然后将一部分静态的始末serve出来,以供前端代码应用。这样的便宜多多:

  1. 左右端支付相对独立
  2. 后端的进程不会影响前端开发
  3. 开行速度更快
  4. 内外端都可以使用自己深谙的技能栈(让前者的学maven,让后端的用gulp都会很不顺手)

但是当集成依然是一个令人头痛的难点。大家往往在合龙的时候才发觉,本来协商的数据结构变了:deliveryAddress字段本来是一个字符串,现在改成数组了(业务爆发了改观,系统现在得以支撑八个快递地址);price字段变成字符串,协商的时候是number;用户邮箱地址多了一个层级等等。这个改变在所难免,而且暴发,那会开支大批量的调剂时间和合并时间,更别提修改将来的回归测试了。

因此唯有使用一个静态服务器,然后提供mock数据是远远不够的。大家需求的mock应该还是能不辱职责:

  1. 前端重视指定格式的mock数据来展开UI开发
  2. 前者的开发和测试都根据那个mock数据
  3. 后端爆发指定格式的mock数据
  4. 后端须求测试来保管生成的mock数据正是前端要求的

粗略,我们必要订立一些契约,并将那些契约作为可以被测试的高中级格式。然后前后端都亟需有测试来行使那些契约。一旦契约暴发变化,则另一方的测试会败北,那样就会使得双方商谈,并下跌集成时的浪费。

一个其实的现象是:前端发现已有的某个契约中,缺乏了一个address的字段,于是就在契约中添加了该字段。然后在UI中校以此字段正确的显示了(当然还安装了字体,字号,颜色等等)。不过后台生成该契约的劳动并没有感知到这一变迁,当运行生成契约部分测试(后台)时,测试会失败了

因为它并没有生成这些字段。于是后端工程师就找前端来商讨,了然事情逻辑之后,他会修改代码,并确保测试通过。那样,当集成的时候,就不会出现UI上少了一个字段,然而什么人也不理解是前者难点,后端问题,仍然数据库难点等。

再者实际的序列中,往往都是八个页面,四个API,多少个本子,多个团体还要举办支付,那样的契约会下跌极度多的调节时间,使得集成相对平缓。

在实践中,契约可以定义为一个JSON文件,或者一个XML的payload。只必要保险前后端共享同一个契约集合来做测试,那么集成工作就会从中收益。一个最简易的花样是:提供一些静态的mock文件,而前者有着发未来台的伸手都被某种机制拦截,并转换成对该静态资源的请求。

  1. moco,基于Java
  2. wiremock,基于Java
  3. sinatra,基于Ruby

看到sinatra被列在这里,可能熟习Ruby的人会反对:它只是一个后端全职能的的程序库啊。之所以列它在此地,是因为sinatra提供了一套简洁出色的DSL,那么些DSL非凡契合Web语言,我找不到更优质的方法来驱动那一个mock
server越发易读,所以就应用了它。

  前后端分离已经是业界所共识的一种开发/布署格局了。所谓的内外端分离,并不是传统行业中的按机关分割,一部分人纯做前端(HTML/CSS/JavaScript/Flex),另一有的人纯做后端,因为那种措施是不坐班的:比如很多公司利用了后端的模板技术(JSP,
Free马克er,
ERB等等),前端的开销和调节需求一个后台Web容器的帮衬,从而不能成功真正的诀别(更不用提在布署的时候,由于动态内容和静态内容混在一起,当设计动态静态分流的时候,处理起来分外麻烦)。关于前后端支出的另一个议论可以参考那里。

  一点背景

  一个出色的Web应用的布局看起来是这么的:

亚洲必赢官网 6

  前后端都分别有自己的费用流程,营造工具,测试集合等等。前后端仅仅通过接口来编程,这几个接口可能是JSON格式的RESTFul的接口,也恐怕是XML的,重点是后台只负责数据的提供和测算,而浑然不处理突显。而前者则承担得到数码,社团数量并显示的劳作。那样结构清晰,关怀点分离,前后端会变得绝对独立并松耦合。

  上述的现象仍然比较突出,大家实际上在事实上条件中会有极度复杂的气象,比如异构的互联网,异构的操作系统等等:

亚洲必赢官网 7

亚洲必赢官网 ,  在事实上的景观中,后端可能还会更复杂,比如用C语言做多少搜集,然后经过Java整合到一个数据仓库,然后该数据仓库又有一层Web
Service,最终若干个那样的Web
Service又被一个Ruby的聚合Service整合在一道回去给前端。在这么一个错综复杂的种类中,后台任意端点的挫折都可能过不去前端的支付流程,由此大家会采纳mock的章程来化解那几个标题:

亚洲必赢官网 8

  这个mock服务器可以启动一个简单的HTTP服务器,然后将一部分静态的情节serve出来,以供前端代码应用。那样的裨益多多:

  1. 左右端支付绝对独立
  2. 后端的速度不会影响前端开发
  3. 启动速度更快
  4. 内外端都得以运用自己深谙的技能栈(让前者的学maven,让后端的用gulp都会很不顺手)

  不过当集成如故是一个令人脑瓜疼的难题。大家反复在合龙的时候才意识,本来协商的数据结构变了:deliveryAddress字段本来是一个字符串,现在成为数组了(业务爆发了改动,系统现在得以支撑三个快递地址);price字段变成字符串,协商的时候是number;用户邮箱地址多了一个层级等等。那么些改动在所难免,而且暴发,那会费用多量的调节时间和购并时间,更别提修改之后的回归测试了。

  所以仅仅使用一个静态服务器,然后提供mock数量是遥远不够的。大家必要的mock相应仍能一挥而就:

  1. 前端依赖指定格式的mock数据来拓展UI开发
  2. 前端的开支和测试都基于这一个mock数据
  3. 后端发生指定格式的mock数据
  4. 后端须求测试来担保生成的mock数据正是前端须要的

  简单的讲,大家需要签订一些契约,并将这个契约作为可以被测试的中游格式。然后前后端都急需有测试来行使那几个契约。一旦契约爆发变化,则另一方的测试会战败,那样就会使得双方共商,并下落集成时的荒废。

  一个实际上的场面是:前端发现已有的某个契约中,缺少了一个address的字段,于是就在契约中添加了该字段。然后在UI中将这么些字段正确的显示了(当然还设置了字体,字号,颜色等等)。但是后台生成该契约的服务并从未感知到这一变更,当运行生成契约部分测试(后台)时,测试会失利了

因为它并不曾生成那么些字段。于是后端工程师就找前端来合计,通晓工作逻辑之后,他会修改代码,并保管测试通过。那样,当集成的时候,就不会出现UI上少了一个字段,可是何人也不通晓是前者难题,后端难题,依旧数据库难题等。

  而且事实上的品种中,往往都是七个页面,几个API,多个本子,四个团体还要举办开发,那样的契约会下降十分多的调剂时间,使得集成相对平缓。

  在实践中,契约可以定义为一个JSON文件,或者一个XML的payload。只需求保险前后端共享同一个契约集合来做测试,那么集成工作就会从中收益。一个最简易的款式是:提供部分静态的mock文本,而前者有着发未来台的哀告都被某种机制拦截,并转换成对该静态资源的呼吁。

  1. moco,基于Java
  2. wiremock,基于Java
  3. sinatra,基于Ruby

  看到sinatra被列在那里,可能熟习Ruby的人会反对:它不过一个后端专职能的的程序库啊。之所以列它在那里,是因为sinatra提供了一套简洁出色的DSL,这个DSL卓绝符合Web语言,我找不到更可以的章程来驱动这几个mock server愈来愈易读,所以就选拔了它。

前言 使用Node做上下端分离的支出情势带来了有的性质及…

一个例子

俺们以那个动用为示范,来表明怎么着在前后端分离之后,有限扶助代码的品质,并下跌集成的工本。这几个动用场景很粗略:所有人都可以观察一个条文列表,每个登陆用户都得以选取自己喜爱的条文,并为之加星。加星之后的条条框框会保留到用户自己的村办基本中。用户界面看起来是如此的:

亚洲必赢官网 9

只是为了专注在我们的中央上,我去掉了诸如登陆,个人大旨之类的页面,即使你是一个已登录用户,然后大家来看望哪些编写测试。

  纵然通过API来解耦前端和后端开发进程,前后端通过RESTFul的接口来通讯,前端的静态内容和后端的动态总结分别支付,分别配备,集成依然是一个绕不开的问题—
前端/后端的运用都可以独自的运作,然而集成起来却不办事。我们需求开销多量的精力来调节,直到上线前仍然没有人有信念有所的接口都是办事的。

  一个例子

  大家以这一个动用为示范,来验证什么在左右端分离之后,保险代码的成色,并下降集成的本钱。那些动用场景很简单:所有人都可以看来一个条目列表,每个登陆用户都得以选用自己喜好的条文,并为之加星。加星之后的条条框框会保留到用户自己的个人中心中。用户界面看起来是那般的:

亚洲必赢官网 10

  可是为了专注在大家的骨干上,我去掉了诸如登陆,个人基本之类的页面,假诺你是一个已报到用户,然后大家来看看哪些编写测试。

前端开发

依照平时的做法,前后端分离之后,大家很不难mock一些数据来协调测试:

XHTML

[ { “id”: 1, “url”:
“”,
“title”: “Python中的 list comprehension 以及 generator”, “publicDate”:
“2015年3月20日” }, { “id”: 2, “url”:
“”,
“title”: “使用inotify/fswatch打造自动监控脚本”, “publicDate”:
“二零一五年八月1日” }, { “id”: 3, “url”:
“”,
“title”: “使用underscore.js打造前端拔取”, “publicDate”: “二〇一五年一月20日”
} ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
    {
        "id": 1,
        "url": "http://abruzzi.github.com/2015/03/list-comprehension-in-python/",
        "title": "Python中的 list comprehension 以及 generator",
        "publicDate": "2015年3月20日"
    },
    {
        "id": 2,
        "url": "http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/",
        "title": "使用inotify/fswatch构建自动监控脚本",
        "publicDate": "2015年2月1日"
    },
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

接下来,一个也许的艺术是通过请求这一个json来测试前台:

JavaScript

$(function() { $.get(‘/mocks/feeds.json’).then(function(feeds) { var
feedList = new Backbone.Collection(extended); var feedListView = new
FeedListView(feedList); $(‘.container’).append(feedListView.render());
}); });

1
2
3
4
5
6
7
8
$(function() {
  $.get(‘/mocks/feeds.json’).then(function(feeds) {
      var feedList = new Backbone.Collection(extended);
      var feedListView = new FeedListView(feedList);
 
      $(‘.container’).append(feedListView.render());
  });
});

那样自然是可以干活的,可是此地发送请求的url并不是终极的,当集成的时候大家又须要修改为真正的url。一个简便的做法是行使Sinatra来做一遍url的变换:

Shell

get ‘/api/feeds’ do content_type ‘application/json’
File.open(‘mocks/feeds.json’).read end

1
2
3
4
get ‘/api/feeds’ do
  content_type ‘application/json’
  File.open(‘mocks/feeds.json’).read
end

如此,当大家和实在的劳动举行集成时,只必要连续到充足服务器就可以了。

小心,我们前日的主题是mocks/feeds.json这几个文件。那么些文件现在的角色就是一个契约,至少对于前端来说是这么的。紧接着,大家的行使须求渲染加星的机能,那就必要其它一个契约:找出脚下用户加星过的有所条条框框,因而大家参与了一个新的契约:

XHTML

[ { “id”: 3, “url”:
“”,
“title”: “使用underscore.js营造前端选取”, “publicDate”: “二零一五年12月20日”
} ]

1
2
3
4
5
6
7
8
[
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

然后在sinatra中进入一个新的照射:

XHTML

get ‘/api/fav-feeds/:id’ do content_type ‘application/json’
File.open(‘mocks/fav-feeds.json’).read end

1
2
3
4
get ‘/api/fav-feeds/:id’ do
  content_type ‘application/json’
  File.open(‘mocks/fav-feeds.json’).read
end

经过那五个请求,大家会收获八个列表,然后根据那三个列表的交集来绘制出具有的星号的事态(有的是空心,有的是实心):

JavaScript

$.when(feeds, favorite).then(function(feeds, favorite) { var ids =
_.pluck(favorite[0], ‘id’); var extended = _.map(feeds[0],
function(feed) { return _.extend(feed, {status: _.includes(ids,
feed.id)}); }); var feedList = new Backbone.Collection(extended); var
feedListView = new FeedListView(feedList);
$(‘.container’).append(feedListView.render()); });

1
2
3
4
5
6
7
8
9
10
11
$.when(feeds, favorite).then(function(feeds, favorite) {
    var ids = _.pluck(favorite[0], ‘id’);
    var extended = _.map(feeds[0], function(feed) {
        return _.extend(feed, {status: _.includes(ids, feed.id)});
    });
 
    var feedList = new Backbone.Collection(extended);
    var feedListView = new FeedListView(feedList);
 
    $(‘.container’).append(feedListView.render());
});

剩余的一个难题是当点击红心时,大家必要发请求给后端,然后更新红心的气象:

JavaScript

toggleFavorite: function(event) { event.preventDefault(); var that =
this; $.post(‘/api/feeds/’+this.model.get(‘id’)).done(function(){ var
status = that.model.get(‘status’); that.model.set(‘status’, !status);
}); }

1
2
3
4
5
6
7
8
toggleFavorite: function(event) {
    event.preventDefault();
    var that = this;
    $.post(‘/api/feeds/’+this.model.get(‘id’)).done(function(){
        var status = that.model.get(‘status’);
        that.model.set(‘status’, !status);
    });
}

此间又多出来一个请求,但是使用Sinatra大家仍旧得以很简单的扶助它:

JavaScript

post ‘/api/feeds/:id’ do end

1
2
post ‘/api/feeds/:id’ do
end

可以观望,在没有后端的情景下,我们一切都开展顺利 —
后端甚至还并未开首做,或者正在由一个速度比大家慢的团队在开发,然而无所谓,他们不会潜移默化大家的。

不仅如此,当大家写完前端的代码之后,可以做一个End2End的测试。由于选用了mock数据,免去了数据库和网络的耗时,这一个End2End的测试会运行的百般快,并且它确实起到了端到端的作用。这一个测试在最终的融会时,还足以用来当UI测试来运作。所谓一举多得。

JavaScript

#encoding: utf-8 require ‘spec_helper’ describe ‘Feeds List Page’ do
let(:list_page) {FeedListPage.new} before do list_page.load end it
‘user can see a banner and some feeds’ do expect(list_page).to
have_banner expect(list_page).to have_feeds end it ‘user can see 3
feeds in the list’ do expect(list_page.all_feeds).to have_feed_items
count: 3 end it ‘feed has some detail information’ do first =
list_page.all_feeds.feed_items.first expect(first.title).to
eql(“Python中的 list comprehension 以及 generator”) end end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#encoding: utf-8
require ‘spec_helper’
 
describe ‘Feeds List Page’ do
  let(:list_page) {FeedListPage.new}
 
  before do
      list_page.load
  end
 
  it ‘user can see a banner and some feeds’ do
      expect(list_page).to have_banner
      expect(list_page).to have_feeds
  end
 
  it ‘user can see 3 feeds in the list’ do
      expect(list_page.all_feeds).to have_feed_items count: 3
  end
 
  it ‘feed has some detail information’ do
      first = list_page.all_feeds.feed_items.first
      expect(first.title).to eql("Python中的 list comprehension 以及 generator")
  end
end

亚洲必赢官网 11

关于怎么样编写那样的测试,可以参照以前写的那篇文章。

  一点背景

  一个非凡的Web应用的布局看起来是这么的:

亚洲必赢官网 12

  前后端都分别有自己的开发流程,创设工具,测试集合等等。前后端仅仅通过接口来编程,这一个接口可能是JSON格式的RESTFul的接口,也说不定是XML的,重点是后台只担负数据的提供和计量,而浑然不处理呈现。而前者则承担得到多少,协会数量并突显的办事。那样结构清晰,关心点分离,前后端会变得相对独立并松耦合。

  上述的光景依旧比较理想,大家实际上在实质上条件中会有极度复杂的场馆,比如异构的网络,异构的操作系统等等:

亚洲必赢官网 13

  在骨子里的情景中,后端可能还会更扑朔迷离,比如用C语言做多少收集,然后通过Java整合到一个数据仓库,然后该数据仓库又有一层Web
Service,最终若干个这么的Web
Service又被一个Ruby的聚合Service整合在共同回去给前端。在那样一个繁杂的系列中,后台任意端点的破产都可能阻塞前端的开支流程,由此我们会动用mock的法门来解决那几个题材:

亚洲必赢官网 14

  这个mock服务器可以启动一个简单易行的HTTP服务器,然后将一些静态的内容serve出来,以供前端代码应用。那样的裨益多多:

  1. 前后端支出相对独立
  2. 后端的进程不会潜移默化前端开发
  3. 起步速度更快
  4. 上下端都可以行使自己熟谙的技巧栈(让前者的学maven,让后端的用gulp都会很不顺手)

  可是当集成依旧是一个让人感冒的问题。大家一再在合龙的时候才察觉,本来协商的数据结构变了:deliveryAddress字段本来是一个字符串,现在改为数组了(业务暴发了变动,系统现在能够支撑七个快递地址);price字段变成字符串,协商的时候是number;用户邮箱地址多了一个层级等等。这一个改动在所难免,而且爆发,那会开销多量的调节时间和集成时间,更别提修改之后的回归测试了。

  所以仅仅使用一个静态服务器,然后提供mock数码是遥远不够的。大家要求的mock应该仍可以完结:

  1. 前端依赖指定格式的mock数据来展开UI开发
  2. 前端的费用和测试都基于那几个mock数据
  3. 后端暴发指定格式的mock数据
  4. 后端必要测试来确保生成的mock数据正是前端需求的

  简单来说,大家须求签订一些契约,并将这一个契约作为可以被测试的中间格式。然后前后端都须求有测试来利用那几个契约。一旦契约发生变化,则另一方的测试会战败,那样就会使得双方协商,并下跌集成时的荒废。

  一个实际上的光景是:前端发现已有的某个契约中,缺乏了一个address的字段,于是就在契约中添加了该字段。然后在UI上将以此字段正确的变现了(当然还安装了字体,字号,颜色等等)。但是后台生成该契约的劳动并没有感知到这一变迁,当运行生成契约部分测试(后台)时,测试会失利了

因为它并从未成形那些字段。于是后端工程师就找前端来商谈,精通工作逻辑之后,他会修改代码,并确保测试通过。这样,当集成的时候,就不会产出UI上少了一个字段,可是什么人也不精晓是前者难点,后端难点,如故数据库难题等。

  而且实际的花色中,往往都是多少个页面,八个API,三个本子,八个团体还要展开销付,那样的契约会下跌卓殊多的调节时间,使得集成相对平缓。

  在实践中,契约可以定义为一个JSON文件,或者一个XML的payload。只须要确保前后端共享同一个契约集合来做测试,那么集成工作就会从中收益。一个最简单易行的款式是:提供一些静态的mock文件,而前者有着发将来台的乞请都被某种机制拦截,并转换成对该静态资源的呼吁。

  1. moco,基于Java
  2. wiremock,基于Java
  3. sinatra,基于Ruby

  看到sinatra被列在那里,可能明白Ruby的人会反对:它但是一个后端全职能的的程序库啊。之所以列它在那边,是因为sinatra提供了一套简洁精粹的DSL,这个DSL可怜契合Web言语,我找不到更突出的法子来驱动那几个mock server进一步易读,所以就采用了它。

  前端开发

  依照常常的做法,前后端分离之后,大家很容易mock有些数量来协调测试:

[
    {
        "id": 1,
        "url": "http://abruzzi.github.com/2015/03/list-comprehension-in-python/",
        "title": "Python中的 list comprehension 以及 generator",
        "publicDate": "2015年3月20日"
    },
    {
        "id": 2,
        "url": "http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/",
        "title": "使用inotify/fswatch构建自动监控脚本",
        "publicDate": "2015年2月1日"
    },
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

  然后,一个也许的点子是因此请求那么些json来测试前台:

$(function() {
  $.get('/mocks/feeds.json').then(function(feeds) {
      var feedList = new Backbone.Collection(extended);
      var feedListView = new FeedListView(feedList);
      $('.container').append(feedListView.render());
  });
});

  那样自然是足以干活的,可是此间发送请求的url并不是最后的,当集成的时候大家又必要修改为真实的url。一个简约的做法是使用Sinatra来做五遍url的转换:

get '/api/feeds' do
  content_type 'application/json'
  File.open('mocks/feeds.json').read
end

  这样,当我们和实际的服务开展集成时,只须求连接到不行服务器就足以了。

  注意,我们明日的为主是mocks/feeds.json本条文件。这么些文件现在的角色就是一个契约,至少对于前端来说是这么的。紧接着,咱们的利用须求渲染加星的意义,那就必要别的一个契约:找出脚下用户加星过的具有条条框框,由此大家参加了一个新的契约:

[
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

  然后在sinatra中进入一个新的照射:

get '/api/fav-feeds/:id' do
  content_type 'application/json'
  File.open('mocks/fav-feeds.json').read
end

  通过这四个请求,我们会收获四个列表,然后根据那五个列表的长短不一来绘制出装有的星号的情形(有的是空心,有的是实心):

$.when(feeds, favorite).then(function(feeds, favorite) {
    var ids = _.pluck(favorite[0], 'id');
    var extended = _.map(feeds[0], function(feed) {
        return _.extend(feed, {status: _.includes(ids, feed.id)});
    });
    var feedList = new Backbone.Collection(extended);
    var feedListView = new FeedListView(feedList);
    $('.container').append(feedListView.render());
});

  剩下的一个题材是当点击红心时,大家须要发请求给后端,然后更新红心的景况:

toggleFavorite: function(event) {
    event.preventDefault();
    var that = this;
    $.post('/api/feeds/'+this.model.get('id')).done(function(){
        var status = that.model.get('status');
        that.model.set('status', !status);
    });
}

  这里又多出来一个伸手,不过使用Sinatra大家如故得以很不难的支撑它:

post '/api/feeds/:id' do
end

  可以见见,在并未后端的情形下,大家全体都开展顺遂 —
后端甚至还一贯不起来做,或者正在由一个速度比大家慢的团队在开发,然则无所谓,他们不会影响我们的。

  不仅如此,当大家写完前端的代码之后,可以做一个End2End的测试。由于应用了mock数据,免去了数据库和互连网的耗时,那个End2End的测试会运作的越发快,并且它的确起到了端到端的成效。那些测试在终极的合一时,仍能用来当UI测试来运作。所谓一举多得。

#encoding: utf-8
require 'spec_helper'
describe 'Feeds List Page' do
  let(:list_page) {FeedListPage.new}
  before do
      list_page.load
  end
  it 'user can see a banner and some feeds' do
      expect(list_page).to have_banner
      expect(list_page).to have_feeds
  end
  it 'user can see 3 feeds in the list' do
      expect(list_page.all_feeds).to have_feed_items count: 3
  end
  it 'feed has some detail information' do
      first = list_page.all_feeds.feed_items.first
      expect(first.title).to eql("Python中的 list comprehension 以及 generator")
  end
end

亚洲必赢官网 15

  关于如何编写这样的测试,可以参见从前写的那篇小说。

后端开发

自己在那几个示例中,后端采纳了spring-boot作为示范,你应当可以很简单将看似的笔触应用到Ruby或者其余语言上。

先是是请求的输入,FeedsController会负责解析呼吁路径,查数据库,最终回到JSON格式的数量。

JavaScript

@Controller @RequestMapping(“/api”) public class FeedsController {
@Autowired private FeedsService feedsService; @Autowired private
UserService userService; public void setFeedsService(FeedsService
feedsService) { this.feedsService = feedsService; } public void
setUserService(UserService userService) { this.userService =
userService; } @RequestMapping(value=”/feeds”, method =
RequestMethod.GET) @ResponseBody public Iterable<Feed> allFeeds()
{ return feedsService.allFeeds(); }
@RequestMapping(value=”/fav-feeds/{userId}”, method = RequestMethod.GET)
@ResponseBody public Iterable<Feed>
favFeeds(@PathVariable(“userId”) Long userId) { return
userService.favoriteFeeds(userId); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Controller
@RequestMapping("/api")
public class FeedsController {
 
    @Autowired
    private FeedsService feedsService;
 
    @Autowired
    private UserService userService;
 
    public void setFeedsService(FeedsService feedsService) {
        this.feedsService = feedsService;
    }
 
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
 
    @RequestMapping(value="/feeds", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> allFeeds() {
        return feedsService.allFeeds();
    }
 
    @RequestMapping(value="/fav-feeds/{userId}", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> favFeeds(@PathVariable("userId") Long userId) {
        return userService.favoriteFeeds(userId);
    }
}

具体查询的底细我们就不做探讨了,感兴趣的可以在篇章结尾处找到代码库的链接。那么有了这几个Controller之后,我们如何测试它吧?或者说,如何让契约变得实际可用呢?

spring-test提供了丰硕美观的DSL来编排测试,大家仅需求一些代码就可以将契约用起来,并实际上的监督接口的改动:

Java

private MockMvc mockMvc; private FeedsService feedsService; private
UserService userService; @Before public void setup() { feedsService =
mock(FeedsService.class); userService = mock(UserService.class);
FeedsController feedsController = new FeedsController();
feedsController.setFeedsService(feedsService);
feedsController.setUserService(userService); mockMvc =
standaloneSetup(feedsController).build(); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private MockMvc mockMvc;
private FeedsService feedsService;
private UserService userService;
 
@Before
public void setup() {
    feedsService = mock(FeedsService.class);
    userService = mock(UserService.class);
 
    FeedsController feedsController = new FeedsController();
    feedsController.setFeedsService(feedsService);
    feedsController.setUserService(userService);
 
    mockMvc = standaloneSetup(feedsController).build();
}

树立了mockmvc之后,大家就足以编制Controller的单元测试了:

JavaScript

<a href=’;
public void shouldResponseWithAllFeeds() throws Exception {
when(feedsService.allFeeds()).thenReturn(Arrays.asList(prepareFeeds()));
mockMvc.perform(get(“/api/feeds”)) .andExpect(status().isOk())
.andExpect(content().contentType(“application/json;charset=UTF-8”))
.andExpect(jsonPath(“$”, hasSize(3)))
.andExpect(jsonPath(“$[0].publishDate”, is(notNullValue()))); }

1
2
3
4
5
6
7
8
9
10
<a href=’http://www.jobbole.com/members/madao’>@Test</a>
public void shouldResponseWithAllFeeds() throws Exception {
    when(feedsService.allFeeds()).thenReturn(Arrays.asList(prepareFeeds()));
 
    mockMvc.perform(get("/api/feeds"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(3)))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

当发送GET请求到/api/feeds上从此,大家希望再次来到状态是200,然后内容是application/json。然后大家预料重临的结果是一个尺寸为3的数组,然后数组中的第四个元素的publishDate字段不为空。

瞩目此处的prepareFeeds方法,事实上它会去加载mocks/feeds.json文件 —
也就是前者用来测试的mock文件:

JavaScript

private Feed[] prepareFeeds() throws IOException { URL resource =
getClass().getResource(“/mocks/feeds.json”); ObjectMapper mapper = new
ObjectMapper(); return mapper.readValue(resource, Feed[].class); }

1
2
3
4
5
private Feed[] prepareFeeds() throws IOException {
    URL resource = getClass().getResource("/mocks/feeds.json");
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(resource, Feed[].class);
}

那般,当后端修改Feed定义(添加/删除/修改字段),或者涂改了mock数据等,都会招致测试败北;而前者修改mock之后,也会造成测试失败— 不要惧怕败北 — 那样的失利会促进一回磋商,并驱动出终极的service的契约。

相应的,测试/api/fav-feeds/{userId}的不二法门接近:

JavaScript

<a href=’;
public void shouldResponseWithUsersFavoriteFeeds() throws Exception {
when(userService.favoriteFeeds(any(Long.class)))
.thenReturn(Arrays.asList(prepareFavoriteFeeds()));
mockMvc.perform(get(“/api/fav-feeds/1”)) .andExpect(status().isOk())
.andExpect(content().contentType(“application/json;charset=UTF-8”))
.andExpect(jsonPath(“$”, hasSize(1)))
.andExpect(jsonPath(“$[0].title”,
is(“使用underscore.js创设前端拔取”)))
.andExpect(jsonPath(“$[0].publishDate”, is(notNullValue()))); }

1
2
3
4
5
6
7
8
9
10
11
12
<a href=’http://www.jobbole.com/members/madao’>@Test</a>
public void shouldResponseWithUsersFavoriteFeeds() throws Exception {
    when(userService.favoriteFeeds(any(Long.class)))
        .thenReturn(Arrays.asList(prepareFavoriteFeeds()));
 
    mockMvc.perform(get("/api/fav-feeds/1"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(1)))
            .andExpect(jsonPath("$[0].title", is("使用underscore.js构建前端应用")))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

  一个例证

  大家以这几个利用为示范,来注脚什么在内外端分离之后,有限帮助代码的质量,并下降集成的资本。那个利用场景很粗略:所有人都足以看出一个条条框框列表,每个登陆用户都可以选拔自己喜爱的条款,并为之加星。加星之后的条目会保留到用户自己的个人中心中。用户界面看起来是这么的:

亚洲必赢官网 16

  不过为了专注在我们的中坚上,我去掉了例如登陆,个人主题之类的页面,假如你是一个已登录用户,然后大家来探望怎么样编写测试。

  后端开发

  我在那个示例中,后端选用了spring-boot用作示范,你应有可以很不难将接近的思路应用到Ruby或者其它语言上。

  首先是请求的输入,FeedsController会顶住解析呼吁路径,查数据库,最终回来JSON格式的多寡。

@Controller
@RequestMapping("/api")
public class FeedsController {
    @Autowired
    private FeedsService feedsService;
    @Autowired
    private UserService userService;
    public void setFeedsService(FeedsService feedsService) {
        this.feedsService = feedsService;
    }
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    @RequestMapping(value="/feeds", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> allFeeds() {
        return feedsService.allFeeds();
    }
    @RequestMapping(value="/fav-feeds/{userId}", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> favFeeds(@PathVariable("userId") Long userId) {
        return userService.favoriteFeeds(userId);
    }
}

  具体查询的底细咱们就不做商量了,感兴趣的可以在篇章结尾处找到代码库的链接。那么有了那几个Controller之后,大家如何测试它吗?或者说,怎么着让契约变得实际可用呢?

sprint-test提供了丰盛美妙的DSL来编排测试,大家仅必要一些代码就可以将契约用起来,并实际上的监督接口的改动:

private MockMvc mockMvc;
private FeedsService feedsService;
private UserService userService;
@Before
public void setup() {
    feedsService = mock(FeedsService.class);
    userService = mock(UserService.class);
    FeedsController feedsController = new FeedsController();
    feedsController.setFeedsService(feedsService);
    feedsController.setUserService(userService);
    mockMvc = standaloneSetup(feedsController).build();
}

  建立了mockmvc之后,我们就足以编制Controller的单元测试了:

@Test
public void shouldResponseWithAllFeeds() throws Exception {
    when(feedsService.allFeeds()).thenReturn(Arrays.asList(prepareFeeds()));
    mockMvc.perform(get("/api/feeds"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(3)))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

  当发送GET请求到/api/feeds上之后,大家期望再次回到状态是200,然后内容是application/json。然后大家预料再次来到的结果是一个长短为3的数组,然后数组中的第二个因素的publishDate字段不为空。

  注意此处的prepareFeeds形式,事实上它会去加载mocks/feeds.json文本
— 也就是前者用来测试的mock文件:

private Feed[] prepareFeeds() throws IOException {
    URL resource = getClass().getResource("/mocks/feeds.json");
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(resource, Feed[].class);
}

  那样,当后端修改Feed概念(添加/删除/修改字段),或者涂改了mock数据等,都会导致测试失利;而前者修改mock之后,也会招致测试败北— 不要害怕失利 — 这样的失败会促进两遍磋商,并驱动出终极的service的契约。

  对应的,测试/api/fav-feeds/{userId}的不二法门接近:

@Test
public void shouldResponseWithUsersFavoriteFeeds() throws Exception {
    when(userService.favoriteFeeds(any(Long.class)))
        .thenReturn(Arrays.asList(prepareFavoriteFeeds()));
    mockMvc.perform(get("/api/fav-feeds/1"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(1)))
            .andExpect(jsonPath("$[0].title", is("使用underscore.js构建前端应用")))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

总结

左右端分离是一件简单的事体,而且集体或者在长期可以见见许多便宜,不过一旦不认真处理集成的题材,分离反而可能会拉动更长的合蛇时间。通过面向契约的艺术来公司各自的测试,能够推动许多的裨益:更高速的End2End测试,更平整的融会,更安全的分离开发等等。

  前端开发

  根据平时的做法,前后端分离之后,大家很容易mock一些多少来自己测试:

[
    {
        "id": 1,
        "url": "http://abruzzi.github.com/2015/03/list-comprehension-in-python/",
        "title": "Python中的 list comprehension 以及 generator",
        "publicDate": "2015年3月20日"
    },
    {
        "id": 2,
        "url": "http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/",
        "title": "使用inotify/fswatch构建自动监控脚本",
        "publicDate": "2015年2月1日"
    },
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

  然后,一个也许的方法是经过请求那一个json来测试前台:

$(function() {
  $.get('/mocks/feeds.json').then(function(feeds) {
      var feedList = new Backbone.Collection(extended);
      var feedListView = new FeedListView(feedList);
      $('.container').append(feedListView.render());
  });
});

  这样自然是可以干活的,可是此间发送请求的url并不是最后的,当集成的时候大家又需求修改为实际的url。一个粗略的做法是行使Sinatra来做三遍url的变换:

get '/api/feeds' do
  content_type 'application/json'
  File.open('mocks/feeds.json').read
end

  那样,当大家和实在的服务开展集成时,只须要三番五次到丰富服务器就可以了。

  注意,咱们现在的为主是mocks/feeds.json以此文件。这几个文件现在的角色就是一个契约,至少对于前端来说是这么的。紧接着,大家的运用需求渲染加星的功用,那就要求此外一个契约:找出最近用户加星过的具备条条框框,由此大家进入了一个新的契约:

[
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

  然后在sinatra中出席一个新的照耀:

get '/api/fav-feeds/:id' do
  content_type 'application/json'
  File.open('mocks/fav-feeds.json').read
end

  通过那多少个请求,大家会获得八个列表,然后根据那多个列表的插花来绘制出装有的星号的景况(有的是空心,有的是实心):

$.when(feeds, favorite).then(function(feeds, favorite) {
    var ids = _.pluck(favorite[0], 'id');
    var extended = _.map(feeds[0], function(feed) {
        return _.extend(feed, {status: _.includes(ids, feed.id)});
    });
    var feedList = new Backbone.Collection(extended);
    var feedListView = new FeedListView(feedList);
    $('.container').append(feedListView.render());
});

  剩下的一个标题是当点击红心时,我们需求发请求给后端,然后更新红心的动静:

toggleFavorite: function(event) {
    event.preventDefault();
    var that = this;
    $.post('/api/feeds/'+this.model.get('id')).done(function(){
        var status = that.model.get('status');
        that.model.set('status', !status);
    });
}

  那里又多出去一个伸手,可是使用Sinatra我们还可以很不难的协理它:

post '/api/feeds/:id' do
end

  可以看来,在未曾后端的景况下,大家任何都开展顺遂 —
后端甚至还不曾起先做,或者正在由一个进程比大家慢的团体在开发,不过无所谓,他们不会影响大家的。

  不仅如此,当大家写完前端的代码之后,可以做一个End2End的测试。由于采用了mock数据,免去了数据库和网络的耗时,那些End2End的测试会运作的百般快,并且它实在起到了端到端的功用。那些测试在最终的集成时,仍能用来当UI测试来运行。所谓一举多得。

#encoding: utf-8
require 'spec_helper'
describe 'Feeds List Page' do
  let(:list_page) {FeedListPage.new}
  before do
      list_page.load
  end
  it 'user can see a banner and some feeds' do
      expect(list_page).to have_banner
      expect(list_page).to have_feeds
  end
  it 'user can see 3 feeds in the list' do
      expect(list_page.all_feeds).to have_feed_items count: 3
  end
  it 'feed has some detail information' do
      first = list_page.all_feeds.feed_items.first
      expect(first.title).to eql("Python中的 list comprehension 以及 generator")
  end
end

亚洲必赢官网 17

  关于什么编写那样的测试,能够参考此前写的那篇文章。

  总结

  前后端分离是一件简单的事情,而且集体或者在长期可以看看众多益处,可是倘诺不认真处理集成的难题,分离反而可能会带来更长的合并时间。通过面向契约的措施来公司各自的测试,可以带来很多的裨益:更连忙的End2End测试,更平整的三合一,更安全的离别开发等等。

代码

上下端的代码我都放到了Gitbub上,感兴趣的可以clone下来自行钻研:

  1. bookmarks-frontend
  2. bookmarks-server

    1 赞 5 收藏 2
    评论

亚洲必赢官网 18

  后端开发

  我在那个示例中,后端选取了spring-boot用作示范,你应有可以很简单将接近的思路应用到Ruby或者其余语言上。

  首先是请求的输入,FeedsController会顶住解析呼吁路径,查数据库,最终回来JSON格式的多少。

@Controller
@RequestMapping("/api")
public class FeedsController {
    @Autowired
    private FeedsService feedsService;
    @Autowired
    private UserService userService;
    public void setFeedsService(FeedsService feedsService) {
        this.feedsService = feedsService;
    }
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    @RequestMapping(value="/feeds", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> allFeeds() {
        return feedsService.allFeeds();
    }
    @RequestMapping(value="/fav-feeds/{userId}", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> favFeeds(@PathVariable("userId") Long userId) {
        return userService.favoriteFeeds(userId);
    }
}

  具体查询的细节我们就不做研讨了,感兴趣的可以在篇章结尾处找到代码库的链接。那么有了这一个Controller之后,大家怎么测试它呢?或者说,如何让契约变得实在可用呢?

sprint-test提供了足够美妙的DSL来编排测试,大家仅须求或多或少代码就足以将契约用起来,并实际的监督接口的改动:

private MockMvc mockMvc;
private FeedsService feedsService;
private UserService userService;
@Before
public void setup() {
    feedsService = mock(FeedsService.class);
    userService = mock(UserService.class);
    FeedsController feedsController = new FeedsController();
    feedsController.setFeedsService(feedsService);
    feedsController.setUserService(userService);
    mockMvc = standaloneSetup(feedsController).build();
}

  建立了mockmvc之后,大家就足以编写Controller的单元测试了:

@Test
public void shouldResponseWithAllFeeds() throws Exception {
    when(feedsService.allFeeds()).thenReturn(Arrays.asList(prepareFeeds()));
    mockMvc.perform(get("/api/feeds"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(3)))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

  当发送GET请求到/api/feeds上之后,大家愿意重临状态是200,然后内容是application/json。然后我们预料重临的结果是一个长短为3的数组,然后数组中的第二个因素的publishDate字段不为空。

  注意此处的prepareFeeds主意,事实上它会去加载mocks/feeds.json文件
— 也就是前者用来测试的mock文件:

private Feed[] prepareFeeds() throws IOException {
    URL resource = getClass().getResource("/mocks/feeds.json");
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(resource, Feed[].class);
}

  那样,当后端修改Feed概念(添加/删除/修改字段),或者涂改了mock数据等,都会促成测试战败;而前者修改mock之后,也会造成测试失利— 不要害怕战败 — 那样的败北会促进三次协商,并驱动出最终的service的契约。

  对应的,测试/api/fav-feeds/{userId}的法子接近:

@Test
public void shouldResponseWithUsersFavoriteFeeds() throws Exception {
    when(userService.favoriteFeeds(any(Long.class)))
        .thenReturn(Arrays.asList(prepareFavoriteFeeds()));
    mockMvc.perform(get("/api/fav-feeds/1"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(1)))
            .andExpect(jsonPath("$[0].title", is("使用underscore.js构建前端应用")))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

  代码

  前后端的代码我都放到了Gitbub上,感兴趣的能够clone下来自行钻研:

  1. bookmarks-frontend
  2. bookmarks-server

  总结

  前后端分离是一件不难的事体,而且集体或者在长时间能够见见成千成万益处,但是若是不认真处理集成的难点,分离反而可能会推动更长的购并时间。通过面向契约的点子来集团各自的测试,可以拉动很多的裨益:更高效的End2End测试,更平整的融会,更安全的离别开发等等。

  代码

  前后端的代码我都放到了Gitbub上,感兴趣的可以clone下来自行钻研:

  1. bookmarks-frontend
  2. bookmarks-server

 

 

网站地图xml地图