详解Spring云端微服务的组件测试

译文
开发 前端
本文将以常见用例的形式和您讨论Spring Cloud微服务组件测试的相关指南和注意事项。

软件开发教父--Martin Fowler在其题为《​​微服务架构的测试策略​​》讲演中,详细诠释了测试不同级别的微服务的概念,其中就提到了如下图所示的“测试金字塔”模型。该模型从下到上分别为:单元、集成、组件、端到端和探索。

而不可否认的是,随着业界广泛采用云端微服务,我们在得益于处理多个可独立部署的组件的同时,需要提高微服务应用的测试级别,并按需增加测试策略的复杂性。下面,我将从使用者的角度出发,以一个Spring Cloud微服务为例,深入探究各种服务组件的相关测试。

服务

我们的Spring Boot微服务示例会具有如下特征:

  • 将启用​​Spring Cloud Netflix​​。它会使用Spring Boot应用的Netflix OSS集成,来执行服务注册与发现、分布式外部配置(Spring Cloud Config)、以及客户端的负载平衡
  • 将与关系型数据库(如PostgreSQL)相集成
  • 将能调用另一个(内部)微服务
  • 将调用第三方(外部)Web服务
  • 将启用Spring Security,以充当OAuth2的资源服务器
  • 将被“隐藏”在API网关服务器,例如​​Spring Cloud Gateway​​的后面

我们将通过Java 11、Apache Maven、Docker、以及一组协作库,“尽早地”在CI/CD管道中,进行单独的服务测试,而无需实际部署或占用其他服务、数据库、甚至是完整的测试环境资源。同时,您可以通过链接--https://github.com/kmandalas/spring-cloud-component-tests,在GitHub上获取该示例的所有代码。

该示例中的“订单跟踪”微服务是由一个Spring Controller、Service和Repository所组成。它公开了两个端点:

  1. GET/api/orders/{trackingNumber}/status:它通过给定的跟踪号,执行数据库查询,来获取相关订单;然后调用FulfillmentService的内部服务,来确定交付的状态;进而让最终外部服务根据状态,调用位置服务来实现定位。这是一个带有有效的JWT、且受保护的API调用。
  2. GET/api/orders:通过查询数据库,以列出所有订单。这是一个受到额外授权限制的、且受保护的API调用。它仅适用于具有“back-office”角色的用户。

组件测试

OrderControllerTest.java类将针对API提供的多种方法,来封装组件测试。例如,我们可以选用包括:​​Maven插件​​、​​JUnit功能​​、​​Spring Boot测试切片​​和分类单元测试、集成测试、组件测试、合同测试等方法。当然,并非所有的测试类别都需要在CI/CD管道中被执行(或重新执行)。鉴于该示例过于简单,我强烈建议您实施适当的分类。

在​​/src/test/resources/application.yml​​中,我们针对属性的测试配置如下:

YAML

server:
port: 0

spring:
application:
name: order-service-test
cloud:
service-registry:
auto-registration:
enabled: false
loadbalancer:
ribbon:
enabled: false
config:
enabled: false
jpa:
show-sql: true

eureka:
client:
enabled: false
service-url:
registerWithEureka: false

okta:
oauth2:
issuer: https://kmandalas/oauth2/default

location-service:
  url: http://localhost:9999/v1/track/

在上述代码段所示中,我们禁用spring.cloud.config、eureka.client和spring.cloud.service-registry.auto-registration的原因在于,方便孤立地测试微服务。因此,既不会有Spring Cloud Config服务器在启动时,为OrderService的配置属性提供服务;也不会有Eureka服务器提供注册,并能够使用它来按需调用FulfillmentService的动态服务发现。

数据库

当出于测试目的而必须与数据库(关系型或NoSQL)集成时,我们通常有如下三种选择:

  1. 使用嵌入式或内存中(in-memory)方案,例如:H2,https://www.h2database.com/
  2. 使用一个能在测试期间可供访问的真实数据库
  3. 使用与生产数据库接近甚至相同的临时数据库

不同的选项所涉及到的测试资源,将会不尽相同。

  • 如果采用第一种方法,将H2进行集成和组件测试,那么由于生产环境的数据库很可能与H2不同,因此您将不得不维护各种独立的DDL和DML脚本。此外,您也可能会用到原生查询、或其他特定于某个数据库的功能。
  • 如果您需要进行端到端或性能测试的话,那么就应该部署真实的数据库,并在测试环境中启动并运行它。对此,现代化的IaC(infrastructure as code,基础设施即代码)工具、以及详尽的​​测试数据管理​​,将可以为项目按需提供灵活性。
  • 在本测试示例中,我们将使用第三种方法,利用​​testcontainers​​​和​​Flyway​​,实现与Spring Boot的配合,而数据库才采用PostgreSQL。在testcontainers的帮助下,我们将在测试的初始化阶段,创建一个临时的dockerized数据库实例。而Flyway将会在这个临时模式(schema)上触发​​​迁移脚本​​(DDL/DML),以便我们的代码将透明地、针对该临时模式运行。而在测试完成时,我们会处理掉这个dockerized数据库。

可见,我们实际上只需要OrderControllerTest类上的@Testcontainers注释,以及如下的静态声明:

Java

@Container
static PostgreSQLContainer database = new PostgreSQLContainer("postgres:12")
.withDatabaseName("tutorial")
.withUsername("kmandalas")
.withPassword("dzone2022");

@DynamicPropertySource
static void setDatasourceProperties(DynamicPropertyRegistry propertyRegistry) {
propertyRegistry.add("spring.datasource.url", database::getJdbcUrl);
propertyRegistry.add("spring.datasource.password", database::getPassword);
propertyRegistry.add("spring.datasource.username", database::getUsername);
}

内部服务调用

我们将使用​​Spring Cloud OpenFeign​​来调用FulfillmentService,它是另一个“内部”的Spring Cloud微服务,可以被注册到Eureka上。在正常执行的情况下,后台的feign客户端能够通过名称定位目标服务实例,实现客户端的负载均衡(如果发现了多个实例的话)。

在我们的测试中,在没有Eureka(或者是​​Consul​​等其他发现机制)的情况下,我们需要通过如下两个方面,尽可能真实地模拟此类集成:

  1. 通过​​WireMock​​启动一个模拟服务器。该服务器能够根据URL的不同模式,来截获请求,并回复由我们提供的模拟响应。
  2. 使用@TestConfiguration来模拟各种FulfillmentService实例的发现,并将其指向WireMock服务器的URI。您可以通过链接--https://github.com/kmandalas/spring-cloud-component-tests/blob/50241126932fce3e9cfc6351291af5857f77806a/src/test/java/gr/kmandalas/dzone/OrderControllerTest.java#L55,查看到此类测试配置。

当然,您也可以使用​​Hoverfly​​作为嵌入式模拟服务器。在本示例里,我们通过如下依赖项设置,来引入WireMock:

XML

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
 </dependency>

通过spring-cloud-starter-contract-stub-runner,WireMock在Spring Boot应用测试套件中的引导被简化了许多,同时这对于​​契约测试​​(contract tests)也是非常实用的。请通过查看Spring Cloud Contract WireMock的链接--https://docs.spring.io/spring-cloud-contract/docs/current/reference/html/project-features.html#features-wiremock,了解更多相关信息。

有了上面的基础,我们只需要使用@AutoConfigureWireMock去注释测试类,并在测试资源目录下的​​JSON文件​​中定义各种WireMock映射即可。

外部服务调用

在集成的过程中,为了能够调用某些外部的(第三方)服务,我们仍然需要依赖有效的WireMock映射(毕竟能够提供的响应多多益善),以便在​​application.yml​​中定义测试URL资源。下面是一个简单的示例:

YAML

location-service:
  url: http://localhost:9999/v1/track/

我们在外部服务的URL端点路径处,提供了WireMock嵌入式服务器运行的主机和端口。端口号虽然不必经过硬编码,但是可以被定义为动态的,以便在CI/CD管道中并行运行多个组件测试,且不会发生端口冲突。

值得一提的是,WireMock不仅可以用于模拟来自RESTful服务的各种JSON响应,还可以模拟基于SOAP的Web服务的响应。

安全

正如前文提到的,Spring Cloud微服务基础设施通常能够合并出一个诸如Spring Cloud Gateway的API网关。据此,我们可以使用OAuth 2.0、JavaScript对象签名和加密(Object Signing and Encryption,JOSE)、以及JSON Web令牌标准的令牌中继模式,来处理用户的身份识别,授权应用程序查看他们的个人资料,以及访问网关后面的安全资源。通常,此类安全设置会由如下组件构成:

  • 单点登录服务器,如​​Keycloak​​​、Cloud Foundry 的用户帐户和​​​身份验证服务器​​​、以及​​诸如Okta​​之类商用的OAuth2身份验证提供程序。
  • Spring Cloud Gateway之类的API网关服务器,将用户帐户的管理和授权委托给单点登录服务器。
  • 资源服务器:在本Spring Boot微服务示例中为OrderService。

针对本测试示例,我们在单独测试Spring Boot微服务时,会采用​​Spring Security​​的SecurityMockMvcRequestPostProcessors。它将使我们能够在MockMvc调用期间,传递有效的JWT,定义权限(即用户角色),并在启用安全性的情况下,测试组件的行为。例如:

Java

mockMvc.perform(get("/api/orders/11212/status").with(jwt())).andExpect(status().isOk())

mockMvc.perform(get("/api/orders/").with(jwt().authorities(new 
SimpleGrantedAuthority("backoffice"))))
.andExpect(status().isOk());

小结

如今,对于成功的产品交付而言,开发人员是否能够在CI/CD管道中,以自动化的方式执行各类测试是至关重要的。希望上述讨论的有关Spring Cloud微服务组件测试的相关指南和注意事项,能够给您的实际项目交付提供帮助。

译者介绍

陈峻 (Julian Chen),51CTO社区编辑,具有十多年的IT项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验;持续以博文、专题和译文等形式,分享前沿技术与新知;经常以线上、线下等方式,开展信息安全类培训与授课。

原文标题:Component Tests for Spring Cloud Microservices,作者:Kyriakos Mandalas和Dimitris Stavroulakis


责任编辑:华轩 来源: 51CTO
相关推荐

2022-10-13 14:14:42

开发微服务测试

2022-10-08 07:31:26

微服务编排体系

2023-02-09 08:01:12

核心组件非阻塞

2018-04-19 09:32:46

2022-05-06 10:03:51

数据库存储微服务

2018-06-01 23:08:01

Spring Clou微服务服务器

2023-12-19 07:56:08

微服务软件测试左移测试

2023-03-24 16:18:08

微服务架构

2017-12-20 15:37:39

Spring Clou微服务架构

2022-01-16 23:10:40

语言服务注册

2019-12-26 15:49:14

微服务架构业务

2017-09-05 14:05:11

微服务spring clou路由

2021-06-30 10:16:54

微服务架构测试

2023-12-19 09:33:40

微服务监控

2011-05-06 11:28:27

微软云端基础构架

2022-01-26 09:36:53

Consul语言微服务

2018-11-15 07:56:26

2020-11-18 09:37:44

微服务

2018-09-17 14:34:34

微服务测试架构

2024-02-06 18:05:54

微服务SpringCloud
点赞
收藏

51CTO技术栈公众号