自从我们谈论测试和应用有效的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中并分发给每个合作伙伴或感兴趣的消费者。

如果我们可以将此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 API的基于合同的测试相关推荐

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

    jax-rs jax-ws 自从我们谈论测试和应用有效的TDD做法以来,已经有一段时间了,特别是与REST(ful) Web服务和API有关的做法. 但是,这个主题永远都不应忘记,特别是在每个人都在做 ...

  2. Java表的设计合同_java毕业设计_springboot框架的基于合同管理系统

    这是一个基于java的毕业设计项目,毕设课题为springboot框架的基于合同管理系统, 是一个采用b/s结构的javaweb项目, 开发工具eclipsei/eclipse, 项目框架jsp+sp ...

  3. ASP.NET Web API 使用Swagger生成在线帮助测试文档

    ASP.NET Web API 使用Swagger生成在线帮助测试文档 Swagger-UI简单而一目了然.它能够纯碎的基于html+javascript实现,只要稍微整合一下便能成为方便的API在线 ...

  4. 赞扬别人团建评论_赞扬精心设计:基于属性的测试如何帮助我成为更好的开发人员...

    赞扬别人团建评论 开发人员的测试工具箱就是其中之一,很少保持不变. 可以肯定的是,一些测试实践已被证明比其他测试更有价值,但是,我们仍在不断寻找更好,更快和更具表现力的方法来测试我们的代码. 基于属性 ...

  5. 赞扬精心设计:基于属性的测试如何帮助我成为更好的开发人员

    开发人员的测试工具箱就是其中之一,很少保持不变. 可以肯定的是,某些测试实践已被证明比其他测试更有价值,但是,我们仍在不断寻找更好,更快和更具表现力的方法来测试我们的代码. 基于属性的测试 是 Jav ...

  6. 基于python渗透测试_Python中基于属性的测试简介

    基于python渗透测试 by Shashi Kumar Raja 由Shashi Kumar Raja Python中基于属性的测试简介 (Intro to property-based testi ...

  7. 测试管理 | 基于风险的测试

    基于风险的测试 风险是指负面或不希望发生的后果或事件发生的可能性.当引起客户.用户.参与者或干系人对产品质量或项目成功的信心减弱的问题可能发生时,风险就存在.当潜在问题主要影响的是产品质量时,它们被称 ...

  8. 拉卡拉电子合同签约测试参数接入

    拉卡拉电子合同签约测试参数接入, <?phpclass ApplyForm {public $version = '1.0'; //接口版本号public $orderNo; //订单编号pub ...

  9. 基于模型的测试的测试设计

    为什么80%的码农都做不了架构师?>>>    TeemuKanstrén是一名资深科学家,目前在芬兰VTT技术研究中心工作,他还是多伦多大学的一名客座博士后.他的工作涉及:以改进行 ...

最新文章

  1. 【探讨】javascript事件机制底层实现原理
  2. Postfix邮件服务器搭建之roundcube webmail安装与配置
  3. 实现MySQL数据库的基本操作(增删改查)
  4. Servlet课程0425(五) sendRedirect实现不同页面共享数据
  5. PyTorch基础-模型的保存和加载-09
  6. vscode使用php调试
  7. 华为获 25 份 5G 合同;ofo 退款用户数超千万;贾跃亭躲豪宅拒收法律文书 | 极客头条...
  8. LC 231 power of 2
  9. jQuery实现点击行(tr)选中某列中CheckBox
  10. oracle多少条commit比较好,oracle什么时候须要commit
  11. oracle 存储过程 实例 循环 给查询赋值 游标取值
  12. 【软件应用开发】小米便签APP维护开发
  13. java 随机抽取数组内容_工具类:随机抽取数组或集合中的几个不重复元素
  14. 计算机中1kb等于多少字节,1kb等于多少个字节
  15. dw做html电邮,DreamWeaver中表单的使用和电子邮件表单的制作
  16. 华为P40怎么解账号锁P40Pro忘记密码ID账号锁解除方案开机设备解锁帐号软件使用方法
  17. IT前沿技术之node.js篇一:Node.js与javascript
  18. 怪兽大乱斗:进入苹果推荐的Creator独立游戏
  19. 剪映怎么去水印,剪映怎么剪辑视频
  20. Arduino灰度传感器PID巡线

热门文章

  1. java使用htmlparser提取网页纯文本例子
  2. Oracle入门(十二J)之同义词
  3. ssm使用全注解实现增删改查案例——applicationContext.xml
  4. Echarts五步法加初体验
  5. Windows.etc\hosts文件
  6. 学习PL/SQL最好的书籍推荐
  7. java流与文件——文本输入输出
  8. 实现简单的注解型MVC框架 —— 低配SpringMVC
  9. 如何把模型表导入数据库
  10. netsuite 数据集成_Java中带有NetSuite数据实体的对象关系映射(ORM)