From 2432cf496f9815ee4a1e7753f961de8c472e7c52 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 18 Dec 2025 10:35:19 +0800 Subject: [PATCH] =?UTF-8?q?test(puzzle):=20=E6=9B=B4=E6=96=B0=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B=E4=BB=A5=E9=80=82=E9=85=8D=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95=E7=AD=BE=E5=90=8D=E5=B9=B6=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E5=94=AF=E4=B8=80=E6=80=A7=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 PuzzleGenerateServiceDeduplicationTest 中引入 CustomFaceSearchStage 类 - 添加 SpringBootTest 注解以支持完整的上下文加载 - 使用 InjectMocks 替代手动构造服务实例 - 修改 recordMapper.updateSuccess 方法调用,增加一个 String 参数 - 新增 SnowFlakeUtilTest 类用于测试雪花ID生成器的唯一性和性能 - 添加高并发环境下的ID唯一性校验逻辑 - 引入对潜在时间戳溢出问题的检测机制 - 增加单线程性能测试方法 testPerformanceSingleThread --- ...uzzleGenerateServiceDeduplicationTest.java | 24 ++--- .../impl/PuzzleGenerateServiceImplTest.java | 4 +- .../ycwl/basic/utils/SnowFlakeUtilTest.java | 92 +++++++++++++++++++ 3 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 src/test/java/com/ycwl/basic/utils/SnowFlakeUtilTest.java diff --git a/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceDeduplicationTest.java b/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceDeduplicationTest.java index 84c49c1a..6560a69f 100644 --- a/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceDeduplicationTest.java +++ b/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceDeduplicationTest.java @@ -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); // 执行两次生成 diff --git a/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImplTest.java b/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImplTest.java index a9048f9d..5c794941 100644 --- a/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImplTest.java +++ b/src/test/java/com/ycwl/basic/puzzle/service/impl/PuzzleGenerateServiceImplTest.java @@ -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"); diff --git a/src/test/java/com/ycwl/basic/utils/SnowFlakeUtilTest.java b/src/test/java/com/ycwl/basic/utils/SnowFlakeUtilTest.java new file mode 100644 index 00000000..59096e6a --- /dev/null +++ b/src/test/java/com/ycwl/basic/utils/SnowFlakeUtilTest.java @@ -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 idSet = ConcurrentHashMap.newKeySet(); + final CountDownLatch latch = new CountDownLatch(threadCount); + + // Use a set to capture any "suspicious" short IDs (potential raw timestamps) + final Set 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)); + } +}