jax-rs jax-ws_信守承诺:针对JAX-RS API的基于合同的测试

jax-rs jax-ws

自从我们谈论测试和应用有效的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

jax-rs jax-ws

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

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

相关文章

手机流量共享 linux,linux – 通过HTB共享带宽和优先处理实时流量,哪种方案更好?...

我想在我们的互联网线路上添加一些流量管理.在阅读了大量文档之后,我认为HFSC对我来说太复杂了(我不了解所有曲线的东西,我担心我永远不会把它弄好),CBQ不推荐,基本上HTB就是通往适合大多数人.我们的内部网络有三个“段”,我想在这些段之间或多或少地分享带宽(至少在开始时).此…

负载均衡解决方案

某网站随着知名度的提高&#xff0c;用户访问量日渐增多&#xff0c;近段时间以来&#xff0c;由于访问量的激增&#xff0c;服务不可用的情况时有发生。针对这种状况&#xff0c;结合实际情况&#xff0c;设计了一套解决方案。 技术实现 1、负载均衡。2台同样配置的linux服务…

pcf8523_PCF上的Spring Cloud合同和Spring Cloud Services

pcf8523最近&#xff0c;我们有一个客户&#xff0c;对于使用Spring Cloud Contract &#xff08;SCC&#xff09;来防止微服务团队之间的API“漂移”&#xff0c;微型开发团队会照顾个体的API&#xff08;构成企业应用程序的一部分&#xff09;&#xff0c;这些客户非常感兴趣…

python二分法查找时间点_python有序查找算法:二分法

二分法是一种快速查找的方法&#xff0c;时间复杂度低&#xff0c;逻辑简单易懂&#xff0c;总的来说就是不断的除以2除以2... 但是需要注意&#xff1a;待查找的序列区间单调有序 例如需要查找有序数组arr里面的某个关键字key的位置&#xff0c;那么首先确认arr的中位数或者中…

linux shell ls -l,linux之ls -l|grep ^-|wc -l命令 Shell 中常見的日志統計方法

轉&#xff1a;http://www.cnblogs.com/senior-engineer/p/6203268.htmlShell 中常見的日志統計方法https://my.oschina.net/waterbear/blog/371845Linux Shell工具grep awk cut sort uniq sort 使用小結http://www.linuxidc.com/Linux/2012-05/61126.htm查看某文件夾下文件的個…

怎么运行aws的示例程序_使Spring Boot应用程序在AWS上无服务器运行

怎么运行aws的示例程序在之前的 几篇 文章中&#xff0c;我描述了如何设置Spring Boot应用程序并在AWS Elastic Beanstalk上运行它。 尽管这是从物理服务器到云服务器的重要一步&#xff0c;但还有更好的可能&#xff01; 走向无服务器 。 这意味着无需花费任何服务器费用&…

自己写的 ORACLE 函数的解读

DECLARE MAXWRITNO INTEGER ; <!--声明了2个变量&#xff0c;变量类型是INTEGER-->LINETY INTEGER; BEGIN <!--表示进入方法体-->----------获取要循环的结果集&#xff0c;APPLINPER班线许可申请表FOR REC IN (select * from $PRDLINE.APPLINPER a where (A.APPP…

python调用linux命令输出结果,Python-运行shell命令并捕获输出

小编典典这个问题的答案取决于你使用的Python版本。最简单的方法是使用以下subprocess.check_output功能&#xff1a;>>> subprocess.check_output([ls, -l])btotal 0\n-rw-r--r-- 1 memyself staff 0 Mar 14 11:04 files\n该check_output功能适用于仍在广泛使用的几乎…

python cls_关于python中的self,ins , cls的解释

关于python中的self,ins,cls的解释参考下面两篇博文self比较好理解&#xff0c;self指的是类实例对象本身(注意&#xff1a;不是类本身) class Person: def _init_(self,name): self.namename def sayhello(self): print My name is:,self.name pPerson(Tom) print p 为什么不是…

脚本语言和工程语言_语言工程中有趣的事情

脚本语言和工程语言如果您阅读此博客&#xff0c;您将知道我坚信语言的力量。 所以&#xff0c;我当然有很大的偏见&#xff0c;但是我感觉语言工程社区正在增长&#xff0c;并且越来越有趣的东西正在涌现。 为此&#xff0c;我认为通过查看社区中正在发生的事情并列出一些我发…

linux useradd 数字,详解linux useradd用户组合权限管理等

1&#xff0c;权限相关概念Rwx任何一个文件都应该由两部分组成&#xff0c;这两部分其实基于文件系统来组织&#xff0c;磁盘分区创建完成后&#xff0c;在高级格式化的时候&#xff0c;就把整个磁盘分区分成两部分&#xff0c;其中一部分是源数据&#xff0c;一部分是来放数据…

windows上的python能否在unix上使用_怎么用python在Windows系统下,生成UNIX格式文件

在Windows下换行时&#xff0c;有两个字符&#xff1a;回车(/r)和换行(/n)。但在Linux下&#xff0c;只有一个换行(/n)可使用unix2dos和dos2unix命令进行格式的转换&#xff1a; 参数&#xff1a; -k 保持输出文件和输入文件的日期时间戳不变 -o file 默认模式 . 将file转换&am…

.dmp数据文件的解读

通过PL/SQL Developer导出表数据&#xff0c;生成后缀名为.dmp的文件&#xff0c;如果你以某个用户例如&#xff1a;HYT2LINEHN访问数据库&#xff0c;那么你可以将这个用户下的所有表导成dmp文件&#xff0c;那么这个dmp文件里就包含了这个用户下的所有表的数据&#xff0c;当…

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

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

c语言程序经过编译以后生成的文件名的后缀为,c语言源文件经过编译后生成文件的后缀是什么...

c语言源文件经过编译后生成文件的后缀是什么c语言源文件经过编译后生成文件的后缀是“.obj”。C语言源程序经过编译程序编译之后&#xff0c;生成一个后缀为“.obj”的文件&#xff0c;最后由称为“连接程序”的软件&#xff0c;把此“.obj”文件与各种库函数连接在一起&#x…

Java中获取系统日期时间/系统时间

int y,m,d,h,mi,s;Calendar calCalendar.getInstance();ycal.get(Calendar.YEAR);mcal.get(Calendar.MONTH) 1;dcal.get(Calendar.DATE);hcal.get(Calendar.HOUR_OF_DAY);mical.get(Calendar.MINUTE);scal.get(Calendar.SECOND);System.out.println("现在时刻是"y&q…

python如何读取csv文件列表页_每25行读取一个csv文件,并使用python传递到列表

我想读取一个文件,并将该文件的每25行转换为一个列表,也就是说,它应该有4个列表,每个列表中包含25个项目(对于一个文件的100行)。我无法获得这个问题的代码。 输入文件看起来像这样,实际上它有100行: {PutRequest: {Item: {id: {S: E1DBEAE3}, value: {M: {result: {N: u0.0015…

Infinispan版本已映射到最低Java版本

我最近一直在与Infinispan交流 &#xff0c;我注意到这种“分布式内存键/值数据存储”的特征之一是它相对积极地被采用&#xff0c;甚至要求更高版本的Java。 根据参考的Infinispan文档 &#xff0c;以下内容将Infinispan发行版映射到最低Java SE版本。 Infinispan版本及其最…

通过PL/SQL developer工具访问远程的Oracle数据库_访问数据库_连接数据库_登录数据库

文章目录工具简介电脑没有安装 Oracle 数据库电脑安装了 Oracle 数据库工具简介 PL/SQL Developer 是 Oracle 数据库开发工具&#xff0c;PL/SQL Developer 功能很强大&#xff0c;可以做为集成调试器&#xff0c;有 SQL 窗口&#xff0c;命令窗口&#xff0c;对象浏览器和性能…

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

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