信守承诺:JAX-RS API的基于合同的测试

自从我们谈论测试和应用有效的TDD实践以来已经有一段时间了,特别是与REST(ful) Web服务和API相关的实践。 但是,这个主题永远都不应忘记,特别是在每个人都在做微服务的世界中,无论它意味着什么,暗示或采取什么措施。

公平地说,基于微服务的体系结构在很多领域大放异彩 ,使组织可以更快地移动和创新。 但是如果没有适当的纪律,这也会使我们的系统变得脆弱,因为它们变得非常松散。 在今天的帖子中,我们将讨论基于合同的测试和消费者驱动的合同,这是一种实用且可靠的技术,可确保我们的微服务兑现其承诺。

那么, 基于合同的测试如何工作? 简而言之,这是一种非常简单的技术,并遵循以下步骤:

  • 提供商(例如服务A )发布其联系方式(或规范),则在此阶段可能甚至无法使用该实现
  • 消费者(例如服务B )遵循此合同(或规范)以实现与服务A的对话
  • 此外,消费者引入了一个测试套件,以验证其对服务A合同履行的期望

对于SOAP Web服务和API,事情很明显,因为以WSDL文件的形式存在明确的契约。 但是在使用REST(ful) API的情况下,拐角处有很多不同的选择( WADL , RAML , Swagger …),并且仍然没有达成一致。 听起来可能很复杂,但请不要沮丧,因为Pact即将解救!

Pact是支持消费者驱动的合同测试的一系列框架。 有许多语言绑定和实现可用,包括JVM, JVM Pact和Scala-Pact 。 为了发展这种多语言生态系统, Pact还包括一个专用规范 ,以提供不同实现之间的互操作性。

太好了, Pact就在这里,阶段已经准备就绪,我们准备好迎接一些真实的代码片段。 让我们假设我们正在使用出色的Apache CXF和JAX-RS 2.0规范开发用于管理人员的REST(ful) Web API。 为简单起见,我们将仅介绍两个端点:

  • POST / people / v1创建新的人
  • GET / people / v1?email = <email>通过电子邮件地址查找人

从本质上讲,我们可能不会打扰他们,而只是将我们的服务合同中的这些最小部分传达给每个人,因此,让消费者自己应对(事实上, Pact支持这种情况)。 但是可以肯定的是,我们不是那样的,我们确实在乎,并且想全面地记录我们的API,可能我们已经很熟悉Swagger了 。 这样,这就是我们的PeopleRestService

@Api(value = "Manage people")
@Path("/people/v1")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class PeopleRestService {@GET@ApiOperation(value = "Find person by e-mail", notes = "Find person by e-mail", response = Person.class)@ApiResponses({@ApiResponse(code = 404, message = "Person with such e-mail doesn't exists", response = GenericError.class)})public Response findPerson(@ApiParam(value = "E-Mail address to lookup for", required = true) @QueryParam("email") final String email) {// implementation here}@POST@ApiOperation(value = "Create new person", notes = "Create new person", response = Person.class)@ApiResponses({@ApiResponse(code = 201, message = "Person created successfully", response = Person.class),@ApiResponse(code = 409, message = "Person with such e-mail already exists", response = GenericError.class)})public Response addPerson(@Context UriInfo uriInfo, @ApiParam(required = true) PersonUpdate person) {// implementation here}
}

目前,实现细节并不重要,但是让我们看一下GenericErrorPersonUpdatePerson类,因为它们是我们服务合同不可分割的一部分。

@ApiModel(description = "Generic error representation")
public class GenericError {@ApiModelProperty(value = "Error message", required = true)private String message;
}@ApiModel(description = "Person resource representation")
public class PersonUpdate {@ApiModelProperty(value = "Person's first name", required = true) private String email;@ApiModelProperty(value = "Person's e-mail address", required = true) private String firstName;@ApiModelProperty(value = "Person's last name", required = true) private String lastName;@ApiModelProperty(value = "Person's age", required = true) private int age;
}@ApiModel(description = "Person resource representation")
public class Person extends PersonUpdate {@ApiModelProperty(value = "Person's identifier", required = true) private String id;
}

优秀的! 一旦我们有了Swagger批注并且打开了Apache CXF Swagger集成 ,我们就可以生成swagger.json规范文件,将其置于Swagger UI中并分发给每个合作伙伴或感兴趣的消费者。

人们休息服务2

人们休息服务1

如果我们可以将此Swagger规范与Pact框架实现一起用作服务合同,那就太好了。 感谢Atlassian ,我们当然可以使用swagger-request-validator来做到这一点, swagger-request-validator是一个用于根据Swagger / OpenAPI规范验证HTTP请求/响应的库,该库也很好地与Pact JVM集成在一起。

太酷了,现在让我们从提供商转向消费者,并尝试找出拥有这样的Swagger规范可以做什么。 事实证明,我们可以做很多事情。 例如,让我们看一下创建新人员的POST操作。 作为客户(或消费者),我们可以用以下形式表达我们的期望:与请求一起提交有效的有效载荷,我们期望提供者返回HTTP状态代码201 ,并且响应有效载荷应该包含一个新的人。分配的标识符。 实际上,将此语句转换为Pact JVM断言非常简单。

@Pact(provider = PROVIDER_ID, consumer = CONSUMER_ID)
public PactFragment addPerson(PactDslWithProvider builder) {return builder.uponReceiving("POST new person").method("POST").path("/services/people/v1").body(new PactDslJsonBody().stringType("email").stringType("firstName").stringType("lastName").numberType("age")).willRespondWith().status(201).matchHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON).body(new PactDslJsonBody().uuid("id").stringType("email").stringType("firstName").stringType("lastName").numberType("age")).toFragment();
}

为了触发合同验证过程,我们将使用很棒的JUnit和非常流行的REST保证框架。 但是在此之前,让我们从上面的代码片段中阐明什么是PROVIDER_IDCONSUMER_ID 。 如您所料, PROVIDER_ID是合同规范的参考。 为简单起见,我们将从运行PeopleRestService端点获取Swagger规范,幸运的是, Spring Boot测试的改进使此任务变得轻而易举。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = PeopleRestConfiguration.class)
public class PeopleRestContractTest {private static final String PROVIDER_ID = "People Rest Service";private static final String CONSUMER_ID = "People Rest Service Consumer";private ValidatedPactProviderRule provider;@Value("${local.server.port}")private int port;@Rulepublic ValidatedPactProviderRule getValidatedPactProviderRule() {if (provider == null) {provider = new ValidatedPactProviderRule("http://localhost:" + port + "/services/swagger.json", null, PROVIDER_ID, this);}return provider;}
}

CONSUMER_ID只是识别消费者的一种方式,对此不多说。 这样,我们准备完成第一个测试用例:

@Test
@PactVerification(value = PROVIDER_ID, fragment = "addPerson")
public void testAddPerson() {given().contentType(ContentType.JSON).body(new PersonUpdate("tom@smith.com", "Tom", "Smith", 60)).post(provider.getConfig().url() + "/services/people/v1");
}

太棒了! 如此简单,只要注意@PactVerification批注的存在,我们就可以通过名称引用相应的验证片段,在这种情况下,它指出了我们之前介绍的addPerson方法。

很好,但是...有什么意义呢? 很高兴您提出这样的要求,因为从现在开始,合同中可能无法向后兼容的任何变更都将破坏我们的测试用例。 例如,如果提供者决定从响应有效负载中删除id属性,则测试用例将失败。 重命名请求有效负载属性,大不,再次,测试用例将失败。 添加新的路径参数? 运气不好,测试用例不能通过。 您可能会走得更远,即使每次向后兼容(即使向后兼容,也要使用swagger-validator.properties进行微调),但每次合同更改都会失败。

validation.response=ERROR
validation.response.body.missing=ERROR

没有一个很好的主意,但是如果您需要它,它仍然在那里。 同样,让我们​​从成功的场景开始,为要寻找的人添加一些其他的GET端点测试用例,例如:

@Pact(provider = PROVIDER_ID, consumer = CONSUMER_ID)
public PactFragment findPerson(PactDslWithProvider builder) {return builder.uponReceiving("GET find person").method("GET").path("/services/people/v1").query("email=tom@smith.com").willRespondWith().status(200).matchHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON).body(new PactDslJsonBody().uuid("id").stringType("email").stringType("firstName").stringType("lastName").numberType("age")).toFragment();
}@Test
@PactVerification(value = PROVIDER_ID, fragment = "findPerson")
public void testFindPerson() {given().contentType(ContentType.JSON).queryParam("email", "tom@smith.com").get(provider.getConfig().url() + "/services/people/v1");
}

请注意,这里我们引入了使用query(“ email=tom@smith.com”)断言的查询字符串验证。 根据可能的结果,让我们还介绍一下不成功的情况,即人员不存在,并且我们期望返回一些错误以及404状态代码,例如:

@Pact(provider = PROVIDER_ID, consumer = CONSUMER_ID)
public PactFragment findNonExistingPerson(PactDslWithProvider builder) {return builder.uponReceiving("GET find non-existing person").method("GET").path("/services/people/v1").query("email=tom@smith.com").willRespondWith().status(404).matchHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON).body(new PactDslJsonBody().stringType("message")).toFragment();
}@Test
@PactVerification(value = PROVIDER_ID, fragment = "findNonExistingPerson")
public void testFindPersonWhichDoesNotExist() {given().contentType(ContentType.JSON).queryParam("email", "tom@smith.com").get(provider.getConfig().url() + "/services/people/v1");
}

真正出色,可维护,可理解且非侵入性的方法,可以解决诸如基于合同的测试和由消费者驱动的合同之类的复杂而重要的问题。 希望这种有点新的测试技术可以帮助您在开发阶段捕获更多问题,从而避免它们有机会泄漏到生产中。

感谢Swagger,我们能够采取一些捷径,但是如果您没有这么奢侈的话, Pact会提供相当丰富的规格,非常欢迎您学习和使用。 无论如何, Pact JVM在帮助您编写小型而简洁的测试用例方面做得非常出色。

完整的项目资源可在Github上找到 。

翻译自: https://www.javacodegeeks.com/2016/11/keep-promises-contract-based-testing-jax-rs-apis.html

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

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

相关文章

P2084 进制转换

原题链接 https://www.luogu.org/problemnew/show/P2084 这个题的思路就是先将输入的数字存到字符数组里&#xff0c;然后求出这一串数字中的非0元素的个数total&#xff0c;并记录最后一位非0元素的位置。输出时&#xff0c;先输出total-1个&#xff0c;最后再补上第total个&a…

关于C++ const 的全面总结

C中的const关键字的用法非常灵活&#xff0c;而使用const将大大改善程序的健壮性&#xff0c;本人根据各方面查到的资料进行总结如下&#xff0c;期望对朋友们有所帮助。 Const 是C中常用的类型修饰符,常类型是指使用类型修饰符const说明的类型&#xff0c;常类型的变量或对象…

国家普通话水平智能测试软件,国家普通话水平智能测试系统注意事项

国家普通话水平智能测试系统注意事项导语&#xff1a;为了让考生更顺利的通过普通话考试&#xff0c;下面是小编给大家提供的国家普通话水平智能测试系统注意事项&#xff0c;大家可以参考阅读&#xff0c;更多详情请关注应届毕业生考试网。一、登录阶段&#xff1a;1. 请正确佩…

从尾到头打印链表

题目描述 输入一个链表&#xff0c;按链表值从尾到头的顺序返回一个ArrayList。 python solution&#xff1a; # -*- coding:utf-8 -*- class ListNode:def __init__(self, x):self.val xself.next Noneclass Solution:# 返回从尾部到头部的列表值序列&#xff0c;例如[1,2,3…

Advanced C++ -- Logic Constness and Bitwise Constness

首先介绍一下什么是Logic Constness和Bitwise Constness&#xff0c;Logic Constness指的是function 后的const修饰&#xff0c;我们只知道这个function被const修饰了&#xff0c;但是并没有任何变量被const&#xff0c;Bitwise Constness指的是变量&#xff08;指针&#xff0…

计算机社团竞选优势6,社团社长竞选稿六篇

社团社长竞选稿六篇在社会一步步向前发展的今天&#xff0c;用到竞选稿的地方越来越多&#xff0c;竞选稿可以帮助竞选者更好地表达。还是对竞选稿一筹莫展吗&#xff1f;以下是小编为大家收集的社团社长竞选稿6篇&#xff0c;仅供参考&#xff0c;希望能够帮助到大家。社团社长…

使用Spring创建用于JUnit测试的JNDI资源

直到最近&#xff0c;我还使用静态方法来设置内存数据库&#xff08;HSQLDB&#xff09;。 我在JUnit测试的setUp / tearDown中调用了这些方法。 当我使用Spring时&#xff0c;这总是让我感到不自然&#xff0c;并且所有内容都应在其应用程序上下文中运行。 创建一个简单的JND…

安装Office Visio 提示Office 16 Click-to-Run Extensibility Component

今天在安装 Office Visio 2016 时&#xff0c;点击安装程序&#xff0c;出现以下错误&#xff1a; 出现这个问题的原因就是你的电脑以前安装过32位的office&#xff0c;卸载时&#xff0c;注册表没有清理干净。 解决方案&#xff1a; 在win10系统的左下搜索框内&#xff0c;输入…

加密安装Kli Linux

从U盘启动然后安装Kali是我们最喜欢并且是运行Kali最快(容易)的方法.为此,我们首先要把Kali的ISO克隆到U盘.如果你经常使用Kali Linux U盘,请在克隆前阅读完整的文档。 Kali Linux安装系列阅读&#xff1a; 用Live U盘安装Kali Linux http://www.linuxidc.com/Linux/2014-05/1…

hp服务器370G5硬盘列阵,hp DL380 g5创建raid阵列安装系统准备工作

RAID 1称为磁盘镜像&#xff0c;原理是把一个磁盘的数据镜像到另一个磁盘上&#xff0c;也就是说数据在写入一块磁盘的同时&#xff0c;会在另一块闲置的磁盘上生成镜像文件&#xff0c;在不影响性能情况下最大限度的保证系统的可靠性和可修复性上&#xff0c;只要系统中任何一…

服务器设备性能说明,OMC服务器硬件性能和配置说明.doc

OMC服务器硬件性能和配置说明PAGEOMC服务器硬件性能和配置说明TIME \ "yyyy年M月" \* MERGEFORMAT 2011年1月 PAGE 5目 录 TOC \t "Heading 1,1,Heading 2,2,Heading 3,4,Heading2 No Number,2,Heading3 No Number,3,About This Chapter,3,Subtitle,2" 第1…

编译错误syntax error : missing ';' before 'type'原因探寻

在VC6中运行以下代码 //main.c #include <stdio.h> int main() { chara[100]; memset(&a, 0, 100); charb; return 0; } / 编译器将会报一个编译错误&#xff0c; syntax error : missing ; before type 这个错误出在 char b; 这一行。 然后将程序改为 //…

谷歌guava_Google Guava BiMaps

谷歌guava接下来的番石榴之旅是另一个有用的收藏类型BiMap 。 实际上&#xff0c;这非常简单&#xff0c;BiMap只是双向地图。 反转地图 普通的Java映射是一组键和值&#xff0c;您可以按键查找值&#xff0c;这非常有用&#xff0c;例如&#xff0c;说我想创建一个&#xff0…

outlook设置263邮件服务器,大神详解win10系统怎么在Outlook中添加263邮箱的详细教程...

win10系统有很多人都喜欢使用,我们操作的过程中常常会碰到对win10系统怎么在Outlook中添加263邮箱的设置方法&#xff0c;想必大家都遇到过需要对win10系统怎么在Outlook中添加263邮箱进行设置的情况吧&#xff0c;那么应该怎么设置win10系统怎么在Outlook中添加263邮箱究竟该怎…

使用DynamoDB映射器将DynamoDB项目映射到对象

以前&#xff0c;我们使用Java创建了DynamoDB表。 对于各种数据库&#xff08;例如sql数据库或nosql&#xff09;&#xff0c;有一组工具可帮助访问&#xff0c;持久化和管理对象/类与基础数据库之间的数据。 例如&#xff0c;对于SQL数据库&#xff0c;我们使用JPA&#xff0…

linux 命令 find -exec 操作的问题

最近有这样一个需求&#xff0c;删掉某目录下的一些文件夹。其实就是名为“CVS”的文件夹&#xff0c;用过CVS的人都知道&#xff0c;CVS会在目录的每一级建立一个名为CVS的文件夹&#xff0c;里面放着CVS相关信息&#xff0c;我需要将某目录下所有的名为“CVS”的文件夹删掉。…

雷林鹏分享:C# 运算符

C# 运算符 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C# 有丰富的内置运算符&#xff0c;分类如下&#xff1a; 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 杂项运算符 本教程将逐一讲解算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符及…

思科服务器 vmware虚拟多少个hba卡,利用Cisco UCS 管理虚拟机网络(上)

Cisco UCS计算系统针对虚拟化环境的网络管理提供了两种解决方案&#xff1a;一种是纯软件的Cisco Nexus 1000V&#xff1b;一种是基于Cisco UCS M81KR网卡的硬件解决方案。Cisco Nexus 1000V是唯一的第三方分布式虚拟交换机&#xff0c;同样实现了交换机的数据功能和控制功能的…

Pycharm的远程代码编辑

作为一个从Java转到Python的程序猿&#xff0c;一直觉得python的远程代码调试能力不如java&#xff0c;远程调试一把需要各种改代码&#xff0c;牵扯到eventlet库的时候&#xff0c;问题就更严重&#xff0c;需要调整eventlet的各种配置&#xff0c;算了还是不用远程调试了&…

L3-020 至多删三个字符 [DP]

这题在网上看到一个非常容易理解的思路&#xff0c;和大家分享一下。 记dp[i][j]为前i个字符删除j个字符后得到不同字符串的数量&#xff0c;可以得到以下两个转移方程 dp[i][j1]dp[i][j1]dp[i-1][j] (删除s[i]) dp[i][j]dp[i][j]dp[i-1][j] (不删除s[i]) 如果只用上述式子&…