You've already forked FrameTour-BE
test(puzzle): 更新测试用例以适配新的方法签名并增强唯一性验证
- 在 PuzzleGenerateServiceDeduplicationTest 中引入 CustomFaceSearchStage 类 - 添加 SpringBootTest 注解以支持完整的上下文加载 - 使用 InjectMocks 替代手动构造服务实例 - 修改 recordMapper.updateSuccess 方法调用,增加一个 String 参数 - 新增 SnowFlakeUtilTest 类用于测试雪花ID生成器的唯一性和性能 - 添加高并发环境下的ID唯一性校验逻辑 - 引入对潜在时间戳溢出问题的检测机制 - 增加单线程性能测试方法 testPerformanceSingleThread
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.ycwl.basic.puzzle.service.impl;
|
||||
|
||||
import com.ycwl.basic.face.pipeline.stages.CustomFaceSearchStage;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleGenerateRequest;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
|
||||
@@ -17,8 +18,10 @@ import com.ycwl.basic.repository.ScenicRepository;
|
||||
import com.ycwl.basic.storage.StorageFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
@@ -36,6 +39,7 @@ import static org.mockito.Mockito.*;
|
||||
* @author Claude
|
||||
* @since 2025-01-21
|
||||
*/
|
||||
@SpringBootTest
|
||||
class PuzzleGenerateServiceDeduplicationTest {
|
||||
|
||||
@Mock
|
||||
@@ -58,6 +62,7 @@ class PuzzleGenerateServiceDeduplicationTest {
|
||||
|
||||
private PuzzleDuplicationDetector duplicationDetector;
|
||||
|
||||
@InjectMocks
|
||||
private PuzzleGenerateServiceImpl service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -65,17 +70,6 @@ class PuzzleGenerateServiceDeduplicationTest {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
// 创建真实的 duplicationDetector 实例
|
||||
duplicationDetector = new PuzzleDuplicationDetector(recordMapper);
|
||||
|
||||
// 手动注入所有依赖
|
||||
service = new PuzzleGenerateServiceImpl(
|
||||
templateMapper,
|
||||
elementMapper,
|
||||
recordMapper,
|
||||
imageRenderer,
|
||||
fillEngine,
|
||||
scenicRepository,
|
||||
duplicationDetector
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +107,7 @@ class PuzzleGenerateServiceDeduplicationTest {
|
||||
}).when(recordMapper).insert(any());
|
||||
|
||||
// Mock更新成功
|
||||
when(recordMapper.updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt()))
|
||||
when(recordMapper.updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt()))
|
||||
.thenReturn(1);
|
||||
|
||||
// 执行
|
||||
@@ -125,7 +119,7 @@ class PuzzleGenerateServiceDeduplicationTest {
|
||||
assertNull(response.getOriginalRecordId());
|
||||
verify(imageRenderer, times(1)).render(any(), any(), any()); // 确实进行了渲染
|
||||
verify(recordMapper, times(1)).insert(any()); // 插入了一条记录
|
||||
verify(recordMapper, times(1)).updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt());
|
||||
verify(recordMapper, times(1)).updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,7 +157,7 @@ class PuzzleGenerateServiceDeduplicationTest {
|
||||
assertEquals("https://example.com/old-image.jpg", response.getImageUrl()); // 复用的URL
|
||||
verify(imageRenderer, never()).render(any(), any(), any()); // 没有进行渲染
|
||||
verify(recordMapper, never()).insert(any()); // 没有插入新记录
|
||||
verify(recordMapper, never()).updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt());
|
||||
verify(recordMapper, never()).updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,7 +234,7 @@ class PuzzleGenerateServiceDeduplicationTest {
|
||||
return 1;
|
||||
}).when(recordMapper).insert(any());
|
||||
|
||||
when(recordMapper.updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt()))
|
||||
when(recordMapper.updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt()))
|
||||
.thenReturn(1);
|
||||
|
||||
// 执行两次生成
|
||||
|
||||
@@ -84,7 +84,7 @@ class PuzzleGenerateServiceImplTest {
|
||||
record.setId(555L);
|
||||
return 1;
|
||||
}).when(recordMapper).insert(any());
|
||||
when(recordMapper.updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())).thenReturn(1);
|
||||
when(recordMapper.updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())).thenReturn(1);
|
||||
|
||||
PuzzleGenerateRequest request = new PuzzleGenerateRequest();
|
||||
request.setTemplateCode("ticket");
|
||||
@@ -131,7 +131,7 @@ class PuzzleGenerateServiceImplTest {
|
||||
record.setId(777L);
|
||||
return 1;
|
||||
}).when(recordMapper).insert(any());
|
||||
when(recordMapper.updateSuccess(anyLong(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())).thenReturn(1);
|
||||
when(recordMapper.updateSuccess(anyLong(), anyString(), anyString(), anyLong(), anyInt(), anyInt(), anyInt())).thenReturn(1);
|
||||
|
||||
PuzzleGenerateRequest request = new PuzzleGenerateRequest();
|
||||
request.setTemplateCode("ticket");
|
||||
|
||||
92
src/test/java/com/ycwl/basic/utils/SnowFlakeUtilTest.java
Normal file
92
src/test/java/com/ycwl/basic/utils/SnowFlakeUtilTest.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package com.ycwl.basic.utils;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Test for SnowFlakeUtil to verify uniqueness under high concurrency.
|
||||
*/
|
||||
public class SnowFlakeUtilTest {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SnowFlakeUtilTest.class);
|
||||
|
||||
@Test
|
||||
public void testIdUniqueness() throws InterruptedException {
|
||||
// Number of threads
|
||||
int threadCount = 1000;
|
||||
// Number of IDs per thread
|
||||
int idCountPerThread = 10000;
|
||||
// Total IDs expected
|
||||
int totalIdCount = threadCount * idCountPerThread;
|
||||
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
|
||||
final Set<String> idSet = ConcurrentHashMap.newKeySet();
|
||||
final CountDownLatch latch = new CountDownLatch(threadCount);
|
||||
|
||||
// Use a set to capture any "suspicious" short IDs (potential raw timestamps)
|
||||
final Set<String> suspiciousIds = ConcurrentHashMap.newKeySet();
|
||||
|
||||
log.info("Starting concurrent snowflake ID generation test...");
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
executorService.execute(() -> {
|
||||
try {
|
||||
for (int j = 0; j < idCountPerThread; j++) {
|
||||
String id = SnowFlakeUtil.getId();
|
||||
idSet.add(id);
|
||||
|
||||
// Check for the potential overflow bug where it returns raw timestamp
|
||||
// A valid snowflake ID (around year 2025) should be much larger than a timestamp
|
||||
// Current timestamp is approx 13 digits (1734...)
|
||||
// Snowflake ID should be approx 18-19 digits
|
||||
if (id.length() < 15) {
|
||||
suspiciousIds.add(id);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error generating ID", e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
latch.await();
|
||||
long end = System.currentTimeMillis();
|
||||
executorService.shutdown();
|
||||
|
||||
log.info("Generated {} IDs in {} ms", totalIdCount, (end - start));
|
||||
|
||||
if (!suspiciousIds.isEmpty()) {
|
||||
log.warn("Found {} suspicious IDs (likely raw timestamps due to sequence overflow): {}", suspiciousIds.size(), suspiciousIds);
|
||||
// We might not fail the test for this if the user only asked for uniqueness,
|
||||
// but it's good to report.
|
||||
// However, if they are raw timestamps, they collide heavily if generated in the same ms.
|
||||
}
|
||||
|
||||
Assertions.assertEquals(totalIdCount, idSet.size(), "Duplicate IDs found!");
|
||||
log.info("Uniqueness test passed. Total unique IDs: {}", idSet.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformanceSingleThread() {
|
||||
long start = System.currentTimeMillis();
|
||||
int count = 100000;
|
||||
for (int i = 0; i < count; i++) {
|
||||
SnowFlakeUtil.getId();
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
log.info("Generated {} IDs in {} ms (Single Thread)", count, (end - start));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user