SpringCloud升級之路2020.0.x版-24.測試Spring Cloud LoadBalancer


本系列代碼地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford

通過單元測試,我們也可以了解下一般我們實現 spring cloud 自定義的基礎組件,怎么去單元測試。

這里的單元測試主要測試三個場景:

  1. 只返回同一個 zone 下的實例,其他 zone 的不會返回
  2. 對於多個請求,每個請求返回的與上次的實例不同。
  3. 對於多線程的每個請求,如果重試,返回的都是不同的實例

同時,我們也需要針對同步和異步兩個配置,分別進行測試,同步和異步兩種配置測試邏輯是一樣的,只是測試的 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM