使用Specs2和客户端API 2.0进行富有表现力的JAX-RS集成测试

毫无疑问, JAX-RS是一项杰出的技术。 即将发布的规范JAX-RS 2.0带来了更多的强大功能,尤其是在客户端API方面。 今天的帖子的主题是JAX-RS服务的集成测试。 有很多出色的测试框架,例如REST可以确保提供帮助,但是我要展示的方式是使用富有表现力的BDD风格。 这是我的意思的示例:

Create new person with email <a@b.com>Given REST client for application deployed at http://localhost:8080When I do POST to rest/api/people?email=a@b.com&firstName=Tommy&lastName=KnockerThen I expect HTTP code 201

看起来像现代BDD框架的典型Given / When / Then风格。 使用静态编译语言,我们在JVM上能达到多近的距离? 事实证明,这要归功于良好的specs2测试工具。

值得一提的是, specs2是一个Scala框架。 尽管我们将编写一些Scala ,但是我们将以非常有经验的Java开发人员熟悉的非常直观的方式来完成它。 测试中的JAX-RS服务是我们在上一篇文章中开发的 。 这里是:

package com.example.rs;import java.util.Collection;import javax.inject.Inject;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;import com.example.model.Person;
import com.example.services.PeopleService;@Path( '/people' ) 
public class PeopleRestService {@Inject private PeopleService peopleService;@Produces( { MediaType.APPLICATION_JSON } )@GETpublic Collection< Person > getPeople( @QueryParam( 'page') @DefaultValue( '1' ) final int page ) {return peopleService.getPeople( page, 5 );}@Produces( { MediaType.APPLICATION_JSON } )@Path( '/{email}' )@GETpublic Person getPeople( @PathParam( 'email' ) final String email ) {return peopleService.getByEmail( email );}@Produces( { MediaType.APPLICATION_JSON  } )@POSTpublic Response addPerson( @Context final UriInfo uriInfo,@FormParam( 'email' ) final String email, @FormParam( 'firstName' ) final String firstName, @FormParam( 'lastName' ) final String lastName ) {peopleService.addPerson( email, firstName, lastName );return Response.created( uriInfo.getRequestUriBuilder().path( email ).build() ).build();}@Produces( { MediaType.APPLICATION_JSON  } )@Path( '/{email}' )@PUTpublic Person updatePerson( @PathParam( 'email' ) final String email, @FormParam( 'firstName' ) final String firstName, @FormParam( 'lastName' )  final String lastName ) {final Person person = peopleService.getByEmail( email );  if( firstName != null ) {person.setFirstName( firstName );}if( lastName != null ) {person.setLastName( lastName );}return person;     }@Path( '/{email}' )@DELETEpublic Response deletePerson( @PathParam( 'email' ) final String email ) {peopleService.removePerson( email );return Response.ok().build();}
}

非常简单的JAX-RS服务来管理人员。 Java实现提供并支持所有基本的HTTP动词 : GETPUTPOSTDELETE 。 为了完整起见,让我也包括服务层的一些方法,因为这些方法引起我们关注的一些例外。

package com.example.services;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;import org.springframework.stereotype.Service;import com.example.exceptions.PersonAlreadyExistsException;
import com.example.exceptions.PersonNotFoundException;
import com.example.model.Person;@Service
public class PeopleService {private final ConcurrentMap< String, Person > persons = new ConcurrentHashMap< String, Person >(); // ...public Person getByEmail( final String email ) {final Person person = persons.get( email );  if( person == null ) {throw new PersonNotFoundException( email );}return person;}public Person addPerson( final String email, final String firstName, final String lastName ) {final Person person = new Person( email );person.setFirstName( firstName );person.setLastName( lastName );if( persons.putIfAbsent( email, person ) != null ) {throw new PersonAlreadyExistsException( email );}return person;}public void removePerson( final String email ) {if( persons.remove( email ) == null ) {throw new PersonNotFoundException( email );}}
}

基于ConcurrentMap的非常简单但可行的实现。 如果请求的电子邮件的人不存在, 则会引发PersonNotFoundException 。 分别在已经存在带有请求电子邮件的人的情况下引发PersonAlreadyExistsException 。 这些异常中的每一个在HTTP代码中都有一个对应项: 404 NOT FOUND409 CONFLICT 。 这就是我们告诉JAX-RS的方式:

package com.example.exceptions;import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;public class PersonAlreadyExistsException extends WebApplicationException {private static final long serialVersionUID = 6817489620338221395L;public PersonAlreadyExistsException( final String email ) {super(Response.status( Status.CONFLICT ).entity( 'Person already exists: ' + email ).build());}
}
package com.example.exceptions;import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;public class PersonNotFoundException extends WebApplicationException {private static final long serialVersionUID = -2894269137259898072L;public PersonNotFoundException( final String email ) {super(Response.status( Status.NOT_FOUND ).entity( 'Person not found: ' + email ).build());}
}

完整的项目托管在GitHub上 。 让我们结束无聊的部分,然后转到最甜蜜的部分: BDD 。 如文档中所述, specs2对Given / When / Then样式提供了很好的支持也就不足为奇了。 因此,使用specs2 ,我们的测试用例将变成这样:

'Create new person with email <a@b.com>' ^ br^'Given REST client for application deployed at ${http://localhost:8080}' ^ client^'When I do POST to ${rest/api/people}' ^ post(Map('email' -> 'a@b.com', 'firstName' -> 'Tommy', 'lastName' -> 'Knocker'))^'Then I expect HTTP code ${201}'  ^ expectResponseCode^'And HTTP header ${Location} to contain ${http://localhost:8080/rest/api/people/a@b.com}' ^ expectResponseHeader^

还不错,但是那些^brclientpostExpectResponseCodeExpectResponseHeader是什么? ^br只是用于支持Given / When / Then链的一些specs2糖。 其他的postExpectResponseCodeExpectResponseHeader只是我们定义用于实际工作的几个函数/变量。 例如, 客户端是一个新的JAX-RS 2.0客户端,我们以此创建(使用Scala语法):

val client: Given[ Client ] = ( baseUrl: String ) => ClientBuilder.newClient( new ClientConfig().property( 'baseUrl', baseUrl ) )

baseUrl来自Given定义本身,它包含在$ {…}构造中。 此外,我们可以看到Given定义具有很强的类型: Given [Client] 。 稍后我们将看到, WhenThen的情况相同 ,它们确实具有相应的强类型when [T,V]Then [V]

流程如下所示:

  • 给定定义开始,该定义返回Client 。
  • 继续何时定义,该定义接受来自给定的 客户并返回响应
  • 最后是数量的Then定义,这些定义接受来自When的 响应并检查实际期望

这是帖子定义的样子(本身就是When [Client,Response] ):

def post( values: Map[ String, Any ] ): When[ Client, Response ] = ( client: Client ) => ( url: String ) =>  client.target( s'${client.getConfiguration.getProperty( 'baseUrl' )}/$url' ).request( MediaType.APPLICATION_JSON ).post( Entity.form( values.foldLeft( new Form() )( ( form, param ) => form.param( param._1, param._2.toString ) ) ),classOf[ Response ] )

最后是ExpectResponseCodeExpectResponseHeader ,它们非常相似,并且具有相同的类型Then [Response]

val expectResponseCode: Then[ Response ] = ( response: Response ) => ( code: String ) => response.getStatus() must_== code.toInt                           val expectResponseHeader: Then[ Response ] = ( response: Response ) => ( header: String, value: String ) =>        response.getHeaderString( header ) should contain( value )

又一个示例,根据JSON有效负载检查响应内容:

'Retrieve existing person with email <a@b.com>' ^ br^'Given REST client for application deployed at ${http://localhost:8080}' ^ client^'When I do GET to ${rest/api/people/a@b.com}' ^ get^'Then I expect HTTP code ${200}' ^ expectResponseCode^'And content to contain ${JSON}' ^ expectResponseContent('''{'email': 'a@b.com', 'firstName': 'Tommy', 'lastName': 'Knocker' }            ''')^

这次我们使用以下get实现来执行GET请求:

val get: When[ Client, Response ] = ( client: Client ) => ( url: String ) =>  client.target( s'${client.getConfiguration.getProperty( 'baseUrl' )}/$url' ).request( MediaType.APPLICATION_JSON ).get( classOf[ Response ] )

尽管specs2具有丰富的匹配器集,可对JSON有效负载执行不同的检查,但我在Scala中使用了spray-json ,这是一种轻量级,干净且简单的JSON实现(的确如此!),这是ExpectResponseContent实现:

def expectResponseContent( json: String ): Then[ Response ] = ( response: Response ) => ( format: String ) => {format match { case 'JSON' => response.readEntity( classOf[ String ] ).asJson must_== json.asJsoncase _ => response.readEntity( classOf[ String ] ) must_== json}
}

最后一个示例(对现有电子邮件进行POST ):

'Create yet another person with same email <a@b.com>' ^ br^'Given REST client for application deployed at ${http://localhost:8080}' ^ client^'When I do POST to ${rest/api/people}' ^ post(Map( 'email' -> 'a@b.com' ))^'Then I expect HTTP code ${409}' ^ expectResponseCode^'And content to contain ${Person already exists: a@b.com}' ^ expectResponseContent^

看起来很棒! 好的,富有表现力的BDD ,使用强类型和静态编译! 当然,可以使用JUnit集成,并且可以与Eclipse一起很好地工作。

不要忘记自己的specs2报告(由maven-specs2-plugin生成): mvn clean test

请在GitHub上查找完整的项目。 另外,请注意,由于我使用的是最新的JAX-RS 2.0里程碑(最终草案),因此发布API时可能会有所变化。

参考: Andriy Redko {devmind}博客上的JCG合作伙伴 Andrey Redko 使用Specs2和客户端API 2.0进行了富有表现力的JAX-RS集成测试 。

翻译自: https://www.javacodegeeks.com/2013/03/expressive-jax-rs-integration-testing-with-specs2-and-client-api-2-0.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/369350.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android设置控件形状,Android控件自定义形状

Android中处理控件的各种形状可以用到Shape&#xff0c;ApiDemos中有相关的例子&#xff0c;在com.example.android.apis.graphics中的ShapeDrawable1类中有很详细的介绍和例子。使用xml的方法也能达到同样的效果&#xff0c;而且更加方便。如下面的代码所示&#xff1a;XML/HT…

赋值

非阻塞赋值产生寄存器&#xff0c;a<b,b<c. a的值传给c需要两个时钟&#xff0c;两条语句同时执行。阻塞赋值ab&#xff0c;bc&#xff0c;两条语句顺序执行&#xff0c;不产生寄存器。 小明教ic4转载于:https://www.cnblogs.com/xiaoxuesheng993/p/7503893.html

python web开发-flask访问请求数据request

Request对象在web应用的开发中是一个非常重要的对象&#xff0c;主要用来获取用户发来的请求数据。 常用属性参考&#xff1a;http://docs.jinkan.org/docs/flask/api.html#flask.request 下面我们以一个表单提交的例子来说明一些常用request属性的使用。 创建一个表单的templa…

ActiveMQ群集,持久订阅者和虚拟主题可助您一臂之力

因此&#xff0c;您希望跨分布式主题使用ActiveMQ进行发布-订阅&#xff0c;并且要可靠。 您可以只使用永久订阅&#xff0c;对不对&#xff1f; 可以&#xff0c;但是&#xff0c;如果将群集与ActiveMQ一起使用&#xff0c;则可能会遇到意外行为。 我最近在一个客户端上&#…

android 对称加密和非对称加密,Android开发加密之对称与非对称加密算法使用案例.pdf...

Android开发加密之对称与非对称加密算法使用案例消息摘要md5&#xff1a;登录注册&#xff0c; sha1对称加密 1.des:Data Encryption Standard&#xff0c;数据加密标准 2.aes&#xff1a;Advanced Encryption Standard &#xff0c;更高级的方式对称加密特点&#xff1a;加…

unity 半透明混合问题_Unity 实时 半透明 阴影 shader

简单阴影制作思路&#xff1a;1&#xff1a;在角色脚底 放置一块平板2&#xff1a;shader中 根据平板传入的矩阵 以及 光照 对角色进行变换3&#xff1a; 得到投影在地面上的阴影4&#xff1a;阴影直接渲染到 屏幕上缺点&#xff1a;上面的阴影无法 增加半透明阴影效果&#xf…

Showplan 逻辑运算符和物理运算符参考

本文档已存档&#xff0c;并且将不进行维护。运算符说明了 SQL Server 如何执行查询或数据操作语言 (DML) 语句。 查询优化器使用运算符生成查询计划&#xff0c;以创建在查询中指定的结果或执行在 DML 语句中指定的操作。 查询计划是由物理运算符组成的一个树。 您可以使用 SE…

在win10中通过Anaconda3安装tensorflow

安装Anaconda3&#xff0c;然后在所有程序中启动“Anaconda Navigator”&#xff0c;如图&#xff1a; 切换到“Enviroments"(环境)中&#xff0c;在右边有个显示环境的列表&#xff0c;默认有"base(root)"&#xff0c;然后单击底部的”Create"按钮创建一个…

ddr5内存上市时间_DDR5内存明年才能上市,SK Hynix已预研DDR6:12Gbps

拼 命 加 载 中 ... 随着去年Q4季度DRAM芯片价格开始下跌,DDR4内存的价格已经有了松动,很多人还期待着8GB内存降回200多元的价格呢。现在的DDR4内存已经有了继任者——DDR5,标准及芯片生产都完成了,不过尚无平台支持,最快今年底才会有DDR5内存上市,更多地还是2020年上市。…

功能接口简介–在Java 8中重新创建的概念

世界各地的所有Java开发人员都将至少使用以下接口之一&#xff1a;java.lang.Runnable&#xff0c;java.awt.event.ActionListener&#xff0c;java.util.Comparator&#xff0c;java.util.concurrent.Callable。 声明的接口之间有一些共同的特征&#xff0c;该特征是它们在接口…

计算机专业职业规划范文800字,计算机专业学生职业生涯规划书

作为一名计算机学生&#xff0c;这门专业比较难学&#xff0c;所以在进入大学后&#xff0c;我必须得为自己做一份职业生涯规划&#xff0c;让自己在大学几年里能完成学业&#xff0c;成功的奔赴社会工作。一、自我分析我虽然是自己选的计算机专业&#xff0c;但是兴趣并不是很…

Python核心模块——urllib模块

urllib模块中的方法 1.urllib.urlopen(url[,data[,proxies]]) 打开一个url的方法&#xff0c;返回一个文件对象&#xff0c;然后可以进行类似文件对象的操作。本例试着打开google >>> import urllib >>> f urllib.urlopen(http://www.google.com.hk/) >&…

HTML 中点击a标签,页面跳转执行过程

HTML链接使用的是<a>标签点击超链接,后台的执行大致如下:<a href"https://www.baidu.com">超链接</a>根据链接地址看出,执行的是https协议点击超链接,首先在本地的hosts文件(C:\Windows\System32\drivers\etc\hosts)中查找是否有与网址匹配的ip如果…

JS中原型与原型链

一. 普通对象与函数对象 JavaScript 中&#xff0c;万物皆对象&#xff01;但对象也是有区别的。分为普通对象和函数对象&#xff0c;Object 、Function等 是 JS 自带的函数对象。下面举例说明。 var o1 {}; var o2 new Object(); var o3 new f1();function f1(){}; var f2…

计算机专业学生求职信500字,计算机专业求职信500字范文

计算机专业求职信500字范文尊敬的领导&#xff1a;您好&#xff01;请恕打扰&#xff0c;我是荆楚理工学院计算机工程学院的一个大学生&#xff0c;即将面临毕业.我很荣幸有机回向您呈上我的个人资料.在投身社会之际,为了找到符合自己专业和兴趣的工作,更好地发挥自己的才能,实…

一年中所有节日的排列顺序_计数问题(二)-排列组合的使用

在计数问题(一)中我们分析了排列和组合的定义&#xff0c;计算方法以及公式的含义。排列组合的基本定义讲述的是从一列元素中分先后&#xff08;排列&#xff09;或不分先后地选出部分元素&#xff0c;其可能的选择方法数。在这一期中我们会更仔细地分析组合的公式的含义&#…

使用Spring数据和Thymeleaf实现Bootstrap分页

Twitter Bootstrap具有非常好的分页UI &#xff0c;在这里我将向您展示如何使用Spring Data Web分页功能和Thymeleaf条件评估功能来实现它。 引导程序中的标准分页 受Rdio启发的简单分页&#xff0c;非常适合应用程序和搜索结果。 大块很难错过&#xff0c;易于扩展&#xff0…

一道前端学习题

对于没参加过互联网企业招聘&#xff0c;或是没有参加过大型互联网企业招聘的人来说&#xff0c;能以这些公司的面试题做为锻炼&#xff0c;无疑是一种非常好的学习和进步的途径。下面是一道腾讯的前端面试题(JS解答)&#xff0c;题目本身在现实中意义不大&#xff0c;主要是考…

codefroces 297E Mystic Carvings

problem&#xff1a;一个圆上依次有1~2*n的数字。每个数字都有且只有另一个数字与他相连。选出三条线&#xff0c;使得每条线的两端之间隔的最少点(只包括被选择的6个点)的个数相等。输入输出格式输入格式&#xff1a;The first line contains integer n(3<n<10^5) — th…

连接计算机和网络传输介质的接口,最常用的网络传输介质和连接设备

T型连接器与BNC接插件是细同轴电缆的连接器&#xff0c;它对网络的可靠性有着重要的影响。同轴电缆与T型连接器是依赖于BNC接插件进行连接的。BNC接插件有手工安装和工具型安装之分&#xff0c;用户可根据实际情况和线路的可靠性选择。RJ&#xff0d;45非屏蔽双绞线连接器有8根…