资讯专栏INFORMATION COLUMN

SpringCloud升级之路2020.0.x版-40. spock 单元测试封装的 WebClie

番茄西红柿 / 2969人阅读

摘要:本系列代码地址我们继续上一节,继续使用测试我们自己封装的测试针对重试测试针对重试针对响应超时,我们需要验证重试仅针对可以重试的方法包括方法以及配置的可重试方法,针对不可重试的方法没有重试。

本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent

我们继续上一节,继续使用 spock 测试我们自己封装的 WebClient

测试针对 readTimeout 重试

针对响应超时,我们需要验证重试仅针对可以重试的方法(包括 GET 方法以及配置的可重试方法),针对不可重试的方法没有重试。我们可以通过 spock 单元测试中,检查对于负载均衡器获取实例方法的调用次数看出来是否有重试

我们通过 httpbin.org 的 /delay/秒 实现 readTimeout,分别验证:

  • 测试 GET 延迟 2 秒返回,超过读取超时,这时候会重试
  • 测试 POST 延迟 3 秒返回,超过读取超时,同时路径在重试路径中,这样也是会重试的
  • 测试 POST 延迟 2 秒返回,超过读取超时,同时路径在重试路径中,这样不会重试

代码如下:

@SpringBootTest(		properties = [				"webclient.configs.testServiceWithCannotConnect.baseUrl=http://testServiceWithCannotConnect",				"webclient.configs.testServiceWithCannotConnect.serviceName=testServiceWithCannotConnect",				"webclient.configs.testService.baseUrl=http://testService",				"webclient.configs.testService.serviceName=testService",				"webclient.configs.testService.responseTimeout=1s",				"webclient.configs.testService.retryablePaths[0]=/delay/3",				"webclient.configs.testService.retryablePaths[1]=/status/4*",				"spring.cloud.loadbalancer.zone=zone1",				"resilience4j.retry.configs.default.maxAttempts=3",				"resilience4j.circuitbreaker.configs.default.failureRateThreshold=50",				"resilience4j.circuitbreaker.configs.default.slidingWindowType=TIME_BASED",				"resilience4j.circuitbreaker.configs.default.slidingWindowSize=5",				//因为重试是 3 次,为了防止断路器打开影响测试,设置为正好比重试多一次的次数,防止触发				//同时我们在测试的时候也需要手动清空断路器统计				"resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=4",				"resilience4j.circuitbreaker.configs.default.recordExceptions=java.lang.Exception"		],		classes = MockConfig)class WebClientUnitTest extends Specification {	@SpringBootApplication	static class MockConfig {	}	@SpringBean	private LoadBalancerClientFactory loadBalancerClientFactory = Mock()	@Autowired	private CircuitBreakerRegistry circuitBreakerRegistry	@Autowired	private Tracer tracer	@Autowired	private ServiceInstanceMetrics serviceInstanceMetrics	@Autowired	private WebClientNamedContextFactory webClientNamedContextFactory	//不同的测试方法的类对象不是同一个对象,会重新生成,保证互相没有影响	def zone1Instance1 = new DefaultServiceInstance(instanceId: "instance1", host: "www.httpbin.org", port: 80, metadata: Map.ofEntries(Map.entry("zone", "zone1")))	def zone1Instance2 = new DefaultServiceInstance(instanceId: "instance2", host: "www.httpbin.org", port: 8081, metadata: Map.ofEntries(Map.entry("zone", "zone1")))	def zone1Instance3 = new DefaultServiceInstance(instanceId: "instance3", host: "httpbin.org", port: 80, metadata: Map.ofEntries(Map.entry("zone", "zone1")))	RoundRobinWithRequestSeparatedPositionLoadBalancer loadBalancerClientFactoryInstance = Spy();	ServiceInstanceListSupplier serviceInstanceListSupplier = Spy();	//所有测试的方法执行前会调用的方法	def setup() {		//初始化 loadBalancerClientFactoryInstance 负载均衡器		loadBalancerClientFactoryInstance.setTracer(tracer)		loadBalancerClientFactoryInstance.setServiceInstanceMetrics(serviceInstanceMetrics)		loadBalancerClientFactoryInstance.setServiceInstanceListSupplier(serviceInstanceListSupplier)	}	def "测试针对 readTimeout 重试"() {		given: "设置 testService 的实例都是正常实例"			loadBalancerClientFactory.getInstance("testService") >> loadBalancerClientFactoryInstance			serviceInstanceListSupplier.get() >> Flux.just(Lists.newArrayList(zone1Instance1, zone1Instance3))		when: "测试 GET 延迟 2 秒返回,超过读取超时"			//清除断路器影响			circuitBreakerRegistry.getAllCircuitBreakers().forEach({ c -> c.reset() })			try {				webClientNamedContextFactory.getWebClient("testService")											.get().uri("/delay/2").retrieve()											.bodyToMono(String.class).block();			} catch (WebClientRequestException e) {				if (e.getCause() in  ReadTimeoutException) {					//读取超时忽略				} else {					throw e;				}			}		then: "每次都会超时所以会重试,根据配置一共有 3 次"			3 * loadBalancerClientFactoryInstance.getInstanceResponseByRoundRobin(*_)		when: "测试 POST 延迟 3 秒返回,超过读取超时,同时路径在重试路径中"			//清除断路器影响			circuitBreakerRegistry.getAllCircuitBreakers().forEach({ c -> c.reset() })			try {				webClientNamedContextFactory.getWebClient("testService")											.post().uri("/delay/3").retrieve()											.bodyToMono(String.class).block();			} catch (WebClientRequestException e) {				if (e.getCause() in  ReadTimeoutException) {					//读取超时忽略				} else {					throw e;				}			}		then: "每次都会超时所以会重试,根据配置一共有 3 次"			3 * loadBalancerClientFactoryInstance.getInstanceResponseByRoundRobin(*_)		when: "测试 POST 延迟 2 秒返回,超过读取超时,这个不能重试"			//清除断路器影响			circuitBreakerRegistry.getAllCircuitBreakers().forEach({ c -> c.reset() })			try {				webClientNamedContextFactory.getWebClient("testService")											.post().uri("/delay/2").retrieve()											.bodyToMono(String.class).block();			} catch (WebClientRequestException e) {				if (e.getCause() in  ReadTimeoutException) {					//读取超时忽略				} else {					throw e;				}			}		then: "没有重试,只有一次调用"			1 * loadBalancerClientFactoryInstance.getInstanceResponseByRoundRobin(*_)	}}

测试非 2xx 响应码返回的重试

对于非 2xx 的响应码,代表请求失败,我们需要测试:

  • 测试 GET 返回 500,会有重试
  • 测试 POST 返回 500,没有重试
  • 测试 POST 返回 400,这个请求路径在重试路径中,会有重试
@SpringBootTest(		properties = [				"webclient.configs.testServiceWithCannotConnect.baseUrl=http://testServiceWithCannotConnect",				"webclient.configs.testServiceWithCannotConnect.serviceName=testServiceWithCannotConnect",				"webclient.configs.testService.baseUrl=http://testService",				"webclient.configs.testService.serviceName=testService",				"webclient.configs.testService.responseTimeout=1s",				"webclient.configs.testService.retryablePaths[0]=/delay/3",				"webclient.configs.testService.retryablePaths[1]=/status/4*",				"spring.cloud.loadbalancer.zone=zone1",				"resilience4j.retry.configs.default.maxAttempts=3",				"resilience4j.circuitbreaker.configs.default.failureRateThreshold=50",				"resilience4j.circuitbreaker.configs.default.slidingWindowType=TIME_BASED",				"resilience4j.circuitbreaker.configs.default.slidingWindowSize=5",				//因为重试是 3 次,为了防止断路器打开影响测试,设置为正好比重试多一次的次数,防止触发				//同时我们在测试的时候也需要手动清空断路器统计				"resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=4",				"resilience4j.circuitbreaker.configs.default.recordExceptions=java.lang.Exception"		],		classes = MockConfig)class WebClientUnitTest extends Specification {	@SpringBootApplication	static class MockConfig {	}	@SpringBean	private LoadBalancerClientFactory loadBalancerClientFactory = Mock()	@Autowired	private CircuitBreakerRegistry circuitBreakerRegistry	@Autowired	private Tracer tracer	@Autowired	private ServiceInstanceMetrics serviceInstanceMetrics	@Autowired	private WebClientNamedContextFactory webClientNamedContextFactory	//不同的测试方法的类对象不是同一个对象,会重新生成,保证互相没有影响	def zone1Instance1 = new DefaultServiceInstance(instanceId: "instance1", host: "www.httpbin.org", port: 80, metadata: Map.ofEntries(Map.entry("zone", "zone1")))	def zone1Instance2 = new DefaultServiceInstance(instanceId: "instance2", host: "www.httpbin.org", port: 8081, metadata: Map.ofEntries(Map.entry("zone", "zone1")))	def zone1Instance3 = new DefaultServiceInstance(instanceId: "instance3", host: "httpbin.org", port: 80, metadata: Map.ofEntries(Map.entry("zone", "zone1")))	RoundRobinWithRequestSeparatedPositionLoadBalancer loadBalancerClientFactoryInstance = Spy();	ServiceInstanceListSupplier serviceInstanceListSupplier = Spy();	//所有测试的方法执行前会调用的方法	def setup() {		//初始化 loadBalancerClientFactoryInstance 负载均衡器		loadBalancerClientFactoryInstance.setTracer(tracer)		loadBalancerClientFactoryInstance.setServiceInstanceMetrics(serviceInstanceMetrics)		loadBalancerClientFactoryInstance.setServiceInstanceListSupplier(serviceInstanceListSupplier)	}	def "测试非 200 响应码返回" () {		given: "设置 testService 的实例都是正常实例"			loadBalancerClientFactory.getInstance("testService") >> loadBalancerClientFactoryInstance			serviceInstanceListSupplier.get() >> Flux.just(Lists.newArrayList(zone1Instance1, zone1Instance3))		when: "测试 GET 返回 500"			//清除断路器影响			circuitBreakerRegistry.getAllCircuitBreakers().forEach({ c -> c.reset() })			try {				webClientNamedContextFactory.getWebClient("testService")											.get().uri("/status/500").retrieve()											.bodyToMono(String.class).block();			} catch (WebClientResponseException e) {				if (e.getStatusCode().is5xxServerError()) {					//5xx忽略				} else {					throw e;				}			}		then: "每次都没有返回 2xx 所以会重试,根据配置一共有 3 次"			3 * loadBalancerClientFactoryInstance.getInstanceResponseByRoundRobin(*_)		when: "测试 POST 返回 500"			//清除断路器影响			circuitBreakerRegistry.getAllCircuitBreakers().forEach({ c -> c.reset() })			try {				webClientNamedContextFactory.getWebClient("testService")											.post().uri("/status/500").retrieve()											.bodyToMono(String.class).block();			} catch (WebClientResponseException e) {				if (e.getStatusCode().is5xxServerError()) {					//5xx忽略				} else {					throw e;				}			}		then: "POST 默认不重试,所以只会调用一次"			1 * loadBalancerClientFactoryInstance.getInstanceResponseByRoundRobin(*_)		when: "测试 POST 返回 400,这个请求路径在重试路径中"			//清除断路器影响			circuitBreakerRegistry.getAllCircuitBreakers().forEach({ c -> c.reset() })			try {				webClientNamedContextFactory.getWebClient("testService")											.post().uri("/status/400").retrieve()											.bodyToMono(String.class).block();			} catch (WebClientResponseException e) {				if (e.getStatusCode().is4xxClientError()) {					//4xx忽略				} else {					throw e;				}			}		then: "路径在重试路径中,所以会重试"			3 * loadBalancerClientFactoryInstance.getInstanceResponseByRoundRobin(*_)	}}

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/124546.html

相关文章

  • SpringCloud升级之路2020.0.x-40. spock 单元测试封装 WebClie

    摘要:在上面打开一个微服务某个实例的一个路径的断路器之后,我们调用其他的路径,无论多少次,都成功并且调用负载均衡器获取服务实例的次数等于调用次数,代表没有重试,也就是没有断路器异常。 本系列代码地址:​​https://github.com/JoJoTec/spring-cloud-parent​​我们来测试下前面封装好的 We...

    番茄西红柿 评论0 收藏2637
  • SpringCloud升级之路2020.0.x-37. 实现异步客户端封装配置管理意义与设计

    摘要:对于异步的请求,使用的是异步客户端即。要实现的配置设计以及使用举例要实现的配置设计以及使用举例首先,我们要实现的,其包含三个重试重试的要在负载均衡之前,因为重试的时候,我们会从负载均衡器获取另一个实例进行重试,而不是在同一个实例上重试多次。 本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 为何需要封装异步 HT...

    fxp 评论0 收藏0
  • SpringCloud升级之路2020.0.x-36. 验证断路器正确性

    摘要:本系列代码地址上一节我们通过单元测试验证了线程隔离的正确性,这一节我们来验证我们断路器的正确性,主要包括验证配置正确加载即我们在配置例如中的加入的的配置被正确加载应用了。本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent上一节我们通过单元测试验证了线程隔离的正确性,这一节我们来验证我们断路器的正确性,主要包括:验证配置正确加载:即我们...

    NotFound 评论0 收藏0
  • SpringCloud升级之路2020.0.x-41. SpringCloudGateway 基本

    摘要:将请求封装成将请求封装成的接口定义是但是最外层传进来的参数是和,需要将他们封装成,这个工作就是在中做的。其实主要任务就是将各种参数封装成除了和本次请求相关的和,还有会话管理器,编码解码器配置,国际化配置还有用于扩展。本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent接下来,将进入我们升级之路的又一大模块,即网关模块。网关模块我们废弃了...

    不知名网友 评论0 收藏0
  • SpringCloud升级之路2020.0.x-41. SpringCloudGateway 基本

    摘要:在这里,会将上下文中载入的拼接成,然后调用其方法的,它是的处理请求业务的起点。添加相关依赖之后,会有这个。路由权重相关配置功能相关实现类,这个我们这里不关心。本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent我们继续分析上一节提到的 WebHandler,经过将请求封装成 ServerWebExchange 的 HttpWebHand...

    不知名网友 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<