You've already forked FrameTour-BE
test(integration): 添加集成回退服务的单元测试
- 验证缓存存在时返回缓存值的功能 - 测试无缓存时调用远程服务并缓存结果 - 验证远程调用失败且无缓存时抛出异常 - 测试清除单个缓存项功能 - 验证清除服务所有缓存项功能 - 测试获取缓存统计信息功能 - 验证并发请求时只调用一次远程服务的互斥锁机制
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
package com.ycwl.basic.integration.common.service;
|
||||
|
||||
import com.ycwl.basic.integration.common.config.IntegrationProperties;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class IntegrationFallbackServiceTest {
|
||||
|
||||
private IntegrationFallbackService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new IntegrationFallbackService(new IntegrationProperties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnCachedValueWhenCacheExists() {
|
||||
String serviceName = "zt-scenic";
|
||||
String cacheKey = "k1";
|
||||
|
||||
// 第一次调用,缓存结果
|
||||
service.executeWithFallback(serviceName, cacheKey, () -> "cached", String.class);
|
||||
|
||||
// 第二次调用,应直接返回缓存,不调用远程
|
||||
AtomicInteger remoteCalls = new AtomicInteger();
|
||||
String result = service.executeWithFallback(serviceName, cacheKey, () -> {
|
||||
remoteCalls.incrementAndGet();
|
||||
return "remote";
|
||||
}, String.class);
|
||||
|
||||
assertEquals("cached", result);
|
||||
assertEquals(0, remoteCalls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallRemoteWhenNoCacheAndCacheResult() {
|
||||
String serviceName = "zt-scenic";
|
||||
String cacheKey = "k2";
|
||||
|
||||
AtomicInteger remoteCalls = new AtomicInteger();
|
||||
String result = service.executeWithFallback(serviceName, cacheKey, () -> {
|
||||
remoteCalls.incrementAndGet();
|
||||
return "remote";
|
||||
}, String.class);
|
||||
|
||||
assertEquals("remote", result);
|
||||
assertEquals(1, remoteCalls.get());
|
||||
assertTrue(service.hasFallbackCache(serviceName, cacheKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenRemoteFailsAndNoCache() {
|
||||
String serviceName = "zt-scenic";
|
||||
String cacheKey = "k3";
|
||||
|
||||
RuntimeException ex = assertThrows(RuntimeException.class, () ->
|
||||
service.executeWithFallback(serviceName, cacheKey, () -> {
|
||||
throw new RuntimeException("boom");
|
||||
}, String.class)
|
||||
);
|
||||
|
||||
assertEquals("boom", ex.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldClearCache() {
|
||||
String serviceName = "zt-scenic";
|
||||
String cacheKey = "k4";
|
||||
|
||||
service.executeWithFallback(serviceName, cacheKey, () -> "value", String.class);
|
||||
assertTrue(service.hasFallbackCache(serviceName, cacheKey));
|
||||
|
||||
service.clearFallbackCache(serviceName, cacheKey);
|
||||
assertFalse(service.hasFallbackCache(serviceName, cacheKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldClearAllCache() {
|
||||
String serviceName = "zt-scenic";
|
||||
|
||||
service.executeWithFallback(serviceName, "key1", () -> "v1", String.class);
|
||||
service.executeWithFallback(serviceName, "key2", () -> "v2", String.class);
|
||||
|
||||
service.clearAllFallbackCache(serviceName);
|
||||
|
||||
assertFalse(service.hasFallbackCache(serviceName, "key1"));
|
||||
assertFalse(service.hasFallbackCache(serviceName, "key2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetStats() {
|
||||
String serviceName = "zt-device";
|
||||
|
||||
service.executeWithFallback(serviceName, "d1", () -> "v1", String.class);
|
||||
service.executeWithFallback(serviceName, "d2", () -> "v2", String.class);
|
||||
|
||||
IntegrationFallbackService.FallbackCacheStats stats = service.getFallbackCacheStats(serviceName);
|
||||
|
||||
assertEquals(serviceName, stats.getServiceName());
|
||||
assertEquals(2, stats.getTotalCacheCount());
|
||||
assertEquals(1, stats.getCacheTtlMinutes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOnlyCallRemoteOnceWithConcurrentRequests() throws InterruptedException {
|
||||
String serviceName = "zt-scenic";
|
||||
String cacheKey = "concurrent-test";
|
||||
int threadCount = 10;
|
||||
|
||||
AtomicInteger remoteCalls = new AtomicInteger();
|
||||
CountDownLatch startLatch = new CountDownLatch(1);
|
||||
CountDownLatch doneLatch = new CountDownLatch(threadCount);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
|
||||
|
||||
// 启动多个线程同时请求
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
startLatch.await(); // 等待同时开始
|
||||
service.executeWithFallback(serviceName, cacheKey, () -> {
|
||||
remoteCalls.incrementAndGet();
|
||||
try {
|
||||
Thread.sleep(50); // 模拟远程调用耗时
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return "result";
|
||||
}, String.class);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
doneLatch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startLatch.countDown(); // 同时放行所有线程
|
||||
doneLatch.await(); // 等待所有线程完成
|
||||
executor.shutdown();
|
||||
|
||||
// 互斥锁生效:只有一个线程真正调用了远程
|
||||
assertEquals(1, remoteCalls.get());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user