这样,我们就实现了自定义的负载均衡器。也理解了 Spring Cloud LoadBalancer 的使用。接下来,我们来单元测试下这些功能。集成测试后面会有单独的章节,不用着急。
单元测试上述功能
通过这届单元测试,我们也可以了解下一般我们实现 spring cloud 自定义的基础组件,怎么去单元测试。
这里的单元测试主要测试三个场景:
只返回同一个 zone 下的实例,其他 zone 的不会返回
对于多个请求,每个请求返回的与上次的实例不同。
对于多线程的每个请求,如果重试,返回的都是不同的实例
编写代码:
LoadBalancerTest
//SpringRunner也包含了MockitoJUnitRunner,所以 @Mock 等注解也生效了
@RunWith(SpringRunner.class)
@SpringBootTest(properties = {LoadBalancerEurekaAutoConfiguration.LOADBALANCER_ZONE + "=zone1"})
public class LoadBalancerTest {
@EnableAutoConfiguration(exclude = EurekaDiscoveryClientConfiguration.class)
@Configuration
public static class App {
@Bean
public DiscoveryClient discoveryClient() {
ServiceInstance zone1Instance1 = Mockito.mock(ServiceInstance.class);
ServiceInstance zone1Instance2 = Mockito.mock(ServiceInstance.class);
ServiceInstance zone2Instance3 = Mockito.mock(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 mock = Mockito.mock(DiscoveryClient.class);
Mockito.when(mock.getInstances("testService"))
.thenReturn(List.of(zone1Instance1, zone1Instance2, zone2Instance3));
return mock;
}
}
@Autowired
private LoadBalancerClientFactory loadBalancerClientFactory;
@Autowired
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下
Assert.assertEquals(server.getMetadata().get("zone"), "zone1");
}
}
/**
* 返回不同的实例
*/
@Test
public void testReturnNext() {
ReactiveLoadBalancer<ServiceInstance> testService =
loadBalancerClientFactory.getInstance("testService");
//获取服务实例
ServiceInstance server1 = Mono.from(testService.choose()).block().getServer();
ServiceInstance server2 = Mono.from(testService.choose()).block().getServer();
//每次选择的是不同实例
Assert.assertNotEquals(server1.getInstanceId(), server2.getInstanceId());
}
/**
* 跨线程,默认情况下是可能返回同一实例的,在我们的实现下,保持
* span 则会返回下一个实例,这样保证多线程环境同一个 request 重试会返回下一实例
* @throws Exception
*/
@Test
public void testSameSpanReturnNext() throws Exception {
Span span = tracer.nextSpan();
//测试 100 次
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(() -> {
//保持 trace,这样就会认为仍然是同一个请求上下文,这样模拟重试
try (Tracer.SpanInScope cleared2 = tracer.withSpanInScope(span)) {
server2.set(Mono.from(testService.choose()).block().getServer());
}
});
thread.start();
thread.join();
System.out.println(i);
Assert.assertNotEquals(server1.getInstanceId(), server2.get().getInstanceId());
}
}
}
}
运行测试,测试通过。
微信搜索“我的编程喵”关注公众号,加作者微信,每日一刷,轻松提升技术,斩获各种offer: