本系列程式碼地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford
通過單元測試,我們也可以瞭解下一般我們實現 spring cloud 自定義的基礎元件,怎麼去單元測試。
這裡的單元測試主要測試三個場景:
- 只返回同一個 zone 下的例項,其他 zone 的不會返回
- 對於多個請求,每個請求返回的與上次的例項不同。
- 對於多執行緒的每個請求,如果重試,返回的都是不同的例項
同時,我們也需要針對同步和非同步兩個配置,分別進行測試,同步和非同步兩種配置測試邏輯是一樣的,只是測試的 Bean 不一樣:
- 同步環境是 DiscoveryClient,非同步環境是 ReactiveDiscoveryClient
- 同步環境負載均衡器是 LoadBalancer,非同步環境負載均衡器是 ReactiveLoadBalancer
同步測試程式碼請參考:LoadBalancerTest.java,非同步測試程式碼請參考:LoadBalancerTest.java
我們這裡使用同步測試程式碼作為例子展示:
//SpringExtension也包含了MockitoJUnitRunner,所以 @Mock 等註解也生效了
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = {LoadBalancerEurekaAutoConfiguration.LOADBALANCER_ZONE + "=zone1"})
public class LoadBalancerTest {
@EnableAutoConfiguration
@Configuration
public static class App {
@Bean
public DiscoveryClient myDiscoveryClient() {
ServiceInstance zone1Instance1 = Mockito.spy(ServiceInstance.class);
ServiceInstance zone1Instance2 = Mockito.spy(ServiceInstance.class);
ServiceInstance zone2Instance3 = Mockito.spy(ServiceInstance.class);
Map<String, String> zone1 = Map.ofEntries(
Map.entry("zone", "zone1")
);
Map<String, String> zone2 = Map.ofEntries(
Map.entry("zone", "zone2")
);
when(zone1Instance1.getMetadata()).thenReturn(zone1);
when(zone1Instance1.getInstanceId()).thenReturn("instance1");
when(zone1Instance2.getMetadata()).thenReturn(zone1);
when(zone1Instance2.getInstanceId()).thenReturn("instance2");
when(zone2Instance3.getMetadata()).thenReturn(zone2);
when(zone2Instance3.getInstanceId()).thenReturn("instance3");
DiscoveryClient spy = Mockito.spy(DiscoveryClient.class);
Mockito.when(spy.getInstances("testService"))
.thenReturn(List.of(zone1Instance1, zone1Instance2, zone2Instance3));
return spy;
}
}
@SpyBean
private LoadBalancerClientFactory loadBalancerClientFactory;
@SpyBean
private Tracer tracer;
/**
* 只返回同一個 zone 下的例項
*/
@Test
public void testFilteredByZone() {
ReactiveLoadBalancer<ServiceInstance> testService =
loadBalancerClientFactory.getInstance("testService");
for (int i = 0; i < 100; i++) {
ServiceInstance server = Mono.from(testService.choose()).block().getServer();
//必須處於和當前例項同一個zone下
Assertions.assertEquals(server.getMetadata().get("zone"), "zone1");
}
}
/**
* 返回不同的例項
*/
@Test
public void testReturnNext() {
ReactiveLoadBalancer<ServiceInstance> testService =
loadBalancerClientFactory.getInstance("testService");
Span span = tracer.nextSpan();
for (int i = 0; i < 100; i++) {
try (Tracer.SpanInScope cleared = tracer.withSpanInScope(span)) {
ServiceInstance server1 = Mono.from(testService.choose()).block().getServer();
ServiceInstance server2 = Mono.from(testService.choose()).block().getServer();
//每次選擇的是不同例項
Assertions.assertNotEquals(server1.getInstanceId(), server2.getInstanceId());
}
}
}
/**
* 跨執行緒,預設情況下是可能返回同一例項的,在我們的實現下,保持
* span 則會返回下一個例項,這樣保證多執行緒環境同一個 request 重試會返回下一例項
*
* @throws Exception
*/
@Test
public void testSameSpanReturnNext() throws Exception {
Span span = tracer.nextSpan();
for (int i = 0; i < 100; i++) {
try (Tracer.SpanInScope cleared = tracer.withSpanInScope(span)) {
ReactiveLoadBalancer<ServiceInstance> testService =
loadBalancerClientFactory.getInstance("testService");
ServiceInstance server1 = Mono.from(testService.choose()).block().getServer();
AtomicReference<ServiceInstance> server2 = new AtomicReference<>();
Thread thread = new Thread(() -> {
try (Tracer.SpanInScope cleared2 = tracer.withSpanInScope(span)) {
server2.set(Mono.from(testService.choose()).block().getServer());
}
});
thread.start();
thread.join();
System.out.println(i);
Assertions.assertNotEquals(server1.getInstanceId(), server2.get().getInstanceId());
}
}
}
}
執行測試,測試通過。
我們這一節使用單元測試驗證我們要實現的這些功能是否有效。下一節,我們將開始分析同步環境下的 Http 客戶端,Open-Feign Client。
微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer: