测试
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.org.flashsalesystem;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class FlashSaleSystemApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
package com.org.flashsalesystem.service;
|
||||
|
||||
import com.org.flashsalesystem.dto.FlashSaleDTO;
|
||||
import com.org.flashsalesystem.entity.FlashSale;
|
||||
import com.org.flashsalesystem.entity.Product;
|
||||
import com.org.flashsalesystem.repository.FlashSaleRepository;
|
||||
import com.org.flashsalesystem.repository.ProductRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* 秒杀服务测试类
|
||||
* 测试秒杀核心功能和并发安全性
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
@Transactional
|
||||
public class FlashSaleServiceTest {
|
||||
|
||||
@Autowired
|
||||
private FlashSaleService flashSaleService;
|
||||
|
||||
@Autowired
|
||||
private ProductRepository productRepository;
|
||||
|
||||
@Autowired
|
||||
private FlashSaleRepository flashSaleRepository;
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
private Product testProduct;
|
||||
private FlashSale testFlashSale;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 创建测试商品
|
||||
testProduct = new Product();
|
||||
testProduct.setName("测试商品");
|
||||
testProduct.setDescription("测试商品描述");
|
||||
testProduct.setPrice(new BigDecimal("100.00"));
|
||||
testProduct.setStock(1000);
|
||||
testProduct.setStatus(1);
|
||||
testProduct = productRepository.save(testProduct);
|
||||
|
||||
// 创建测试秒杀活动
|
||||
testFlashSale = new FlashSale();
|
||||
testFlashSale.setProductId(testProduct.getId());
|
||||
testFlashSale.setFlashPrice(new BigDecimal("50.00"));
|
||||
testFlashSale.setFlashStock(100);
|
||||
testFlashSale.setStartTime(LocalDateTime.now().minusMinutes(1));
|
||||
testFlashSale.setEndTime(LocalDateTime.now().plusHours(1));
|
||||
testFlashSale.setStatus(2); // 进行中
|
||||
testFlashSale = flashSaleRepository.save(testFlashSale);
|
||||
|
||||
// 预热秒杀数据到Redis
|
||||
flashSaleService.preloadFlashSale(testFlashSale.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试创建秒杀活动
|
||||
*/
|
||||
@Test
|
||||
void testCreateFlashSale() {
|
||||
// 创建新商品
|
||||
Product newProduct = new Product();
|
||||
newProduct.setName("新测试商品");
|
||||
newProduct.setPrice(new BigDecimal("200.00"));
|
||||
newProduct.setStock(500);
|
||||
newProduct.setStatus(1);
|
||||
newProduct = productRepository.save(newProduct);
|
||||
|
||||
// 创建秒杀活动DTO
|
||||
FlashSaleDTO.CreateDTO createDTO = new FlashSaleDTO.CreateDTO();
|
||||
createDTO.setProductId(newProduct.getId());
|
||||
createDTO.setFlashPrice(new BigDecimal("100.00"));
|
||||
createDTO.setFlashStock(50);
|
||||
createDTO.setStartTime(LocalDateTime.now().plusMinutes(10));
|
||||
createDTO.setEndTime(LocalDateTime.now().plusHours(2));
|
||||
|
||||
// 创建秒杀活动
|
||||
FlashSaleDTO result = flashSaleService.createFlashSale(createDTO);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(newProduct.getId(), result.getProductId());
|
||||
assertEquals(new BigDecimal("100.00"), result.getFlashPrice());
|
||||
assertEquals(50, result.getFlashStock());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试单用户参与秒杀
|
||||
*/
|
||||
@Test
|
||||
void testSingleUserParticipateFlashSale() {
|
||||
Long userId = 1L;
|
||||
|
||||
FlashSaleDTO.ParticipateDTO participateDTO = new FlashSaleDTO.ParticipateDTO();
|
||||
participateDTO.setFlashSaleId(testFlashSale.getId());
|
||||
participateDTO.setQuantity(1);
|
||||
|
||||
FlashSaleDTO.ResultDTO result = flashSaleService.participateFlashSale(userId, participateDTO);
|
||||
|
||||
assertTrue(result.getSuccess());
|
||||
assertNotNull(result.getOrderId());
|
||||
assertEquals(testFlashSale.getId(), result.getFlashSaleId());
|
||||
assertEquals(testProduct.getId(), result.getProductId());
|
||||
assertEquals(1, result.getQuantity());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试重复参与秒杀
|
||||
*/
|
||||
@Test
|
||||
void testDuplicateParticipation() {
|
||||
Long userId = 2L;
|
||||
|
||||
FlashSaleDTO.ParticipateDTO participateDTO = new FlashSaleDTO.ParticipateDTO();
|
||||
participateDTO.setFlashSaleId(testFlashSale.getId());
|
||||
participateDTO.setQuantity(1);
|
||||
|
||||
// 第一次参与
|
||||
FlashSaleDTO.ResultDTO result1 = flashSaleService.participateFlashSale(userId, participateDTO);
|
||||
assertTrue(result1.getSuccess());
|
||||
|
||||
// 第二次参与(应该失败)
|
||||
FlashSaleDTO.ResultDTO result2 = flashSaleService.participateFlashSale(userId, participateDTO);
|
||||
assertFalse(result2.getSuccess());
|
||||
assertTrue(result2.getMessage().contains("已经参与过"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试库存不足情况
|
||||
*/
|
||||
@Test
|
||||
void testInsufficientStock() {
|
||||
// 将库存设置为0
|
||||
String stockKey = "flashsale_stock:" + testFlashSale.getId();
|
||||
redisService.set(stockKey, 0);
|
||||
|
||||
Long userId = 3L;
|
||||
FlashSaleDTO.ParticipateDTO participateDTO = new FlashSaleDTO.ParticipateDTO();
|
||||
participateDTO.setFlashSaleId(testFlashSale.getId());
|
||||
participateDTO.setQuantity(1);
|
||||
|
||||
FlashSaleDTO.ResultDTO result = flashSaleService.participateFlashSale(userId, participateDTO);
|
||||
|
||||
assertFalse(result.getSuccess());
|
||||
assertTrue(result.getMessage().contains("售罄"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试并发秒杀安全性
|
||||
*/
|
||||
@Test
|
||||
void testConcurrentFlashSale() throws InterruptedException {
|
||||
int threadCount = 50;
|
||||
int stockCount = 10;
|
||||
|
||||
// 设置较小的库存用于测试
|
||||
String stockKey = "flashsale_stock:" + testFlashSale.getId();
|
||||
redisService.set(stockKey, stockCount);
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
|
||||
CountDownLatch latch = new CountDownLatch(threadCount);
|
||||
AtomicInteger successCount = new AtomicInteger(0);
|
||||
AtomicInteger failCount = new AtomicInteger(0);
|
||||
|
||||
// 模拟多个用户同时参与秒杀
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
final Long userId = (long) (i + 100); // 避免与其他测试冲突
|
||||
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
FlashSaleDTO.ParticipateDTO participateDTO = new FlashSaleDTO.ParticipateDTO();
|
||||
participateDTO.setFlashSaleId(testFlashSale.getId());
|
||||
participateDTO.setQuantity(1);
|
||||
|
||||
FlashSaleDTO.ResultDTO result = flashSaleService.participateFlashSale(userId, participateDTO);
|
||||
|
||||
if (result.getSuccess()) {
|
||||
successCount.incrementAndGet();
|
||||
} else {
|
||||
failCount.incrementAndGet();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failCount.incrementAndGet();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
latch.await();
|
||||
executor.shutdown();
|
||||
|
||||
// 验证结果
|
||||
assertEquals(threadCount, successCount.get() + failCount.get());
|
||||
assertEquals(stockCount, successCount.get()); // 成功数量应该等于库存数量
|
||||
assertTrue(failCount.get() > 0); // 应该有失败的请求
|
||||
|
||||
// 验证Redis中的剩余库存
|
||||
Integer remainingStock = flashSaleService.getFlashSaleStock(testFlashSale.getId());
|
||||
assertEquals(0, remainingStock); // 库存应该为0
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试获取秒杀活动详情
|
||||
*/
|
||||
@Test
|
||||
void testGetFlashSaleDetail() {
|
||||
FlashSaleDTO result = flashSaleService.getFlashSaleDTOById(testFlashSale.getId());
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(testFlashSale.getId(), result.getId());
|
||||
assertEquals(testProduct.getId(), result.getProductId());
|
||||
assertEquals(testProduct.getName(), result.getProductName());
|
||||
assertEquals(testFlashSale.getFlashPrice(), result.getFlashPrice());
|
||||
assertEquals(testProduct.getPrice(), result.getOriginalPrice());
|
||||
assertTrue(result.getCanParticipate()); // 应该可以参与
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试获取正在进行的秒杀活动
|
||||
*/
|
||||
@Test
|
||||
void testGetActiveFlashSales() {
|
||||
var activeFlashSales = flashSaleService.getActiveFlashSales();
|
||||
|
||||
assertNotNull(activeFlashSales);
|
||||
assertTrue(activeFlashSales.size() > 0);
|
||||
|
||||
// 验证返回的活动包含我们的测试活动
|
||||
boolean found = activeFlashSales.stream()
|
||||
.anyMatch(fs -> fs.getId().equals(testFlashSale.getId()));
|
||||
assertTrue(found);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试秒杀活动预热
|
||||
*/
|
||||
@Test
|
||||
void testPreloadFlashSale() {
|
||||
// 清除缓存
|
||||
String stockKey = "flashsale_stock:" + testFlashSale.getId();
|
||||
redisService.delete(stockKey);
|
||||
|
||||
// 预热
|
||||
flashSaleService.preloadFlashSale(testFlashSale.getId());
|
||||
|
||||
// 验证缓存
|
||||
Integer stock = flashSaleService.getFlashSaleStock(testFlashSale.getId());
|
||||
assertEquals(testFlashSale.getFlashStock(), stock);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试获取秒杀库存
|
||||
*/
|
||||
@Test
|
||||
void testGetFlashSaleStock() {
|
||||
Integer stock = flashSaleService.getFlashSaleStock(testFlashSale.getId());
|
||||
assertEquals(testFlashSale.getFlashStock(), stock);
|
||||
|
||||
// 测试不存在的秒杀活动
|
||||
Integer nonExistentStock = flashSaleService.getFlashSaleStock(99999L);
|
||||
assertEquals(0, nonExistentStock);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package com.org.flashsalesystem.service;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Redis服务测试类
|
||||
* 测试Redis五种数据类型的操作
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
public class RedisServiceTest {
|
||||
|
||||
private static final String TEST_KEY_PREFIX = "test:";
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 清理测试数据
|
||||
cleanupTestData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试String类型操作
|
||||
*/
|
||||
@Test
|
||||
void testStringOperations() {
|
||||
String key = TEST_KEY_PREFIX + "string";
|
||||
String value = "test_value";
|
||||
|
||||
// 测试设置和获取
|
||||
redisService.set(key, value);
|
||||
Object result = redisService.get(key);
|
||||
assertEquals(value, result);
|
||||
|
||||
// 测试带过期时间的设置
|
||||
redisService.set(key + "_expire", value, 1, TimeUnit.SECONDS);
|
||||
assertTrue(redisService.exists(key + "_expire"));
|
||||
|
||||
// 测试递增
|
||||
String counterKey = TEST_KEY_PREFIX + "counter";
|
||||
Long count1 = redisService.incr(counterKey);
|
||||
assertEquals(1L, count1);
|
||||
|
||||
Long count2 = redisService.incrBy(counterKey, 5);
|
||||
assertEquals(6L, count2);
|
||||
|
||||
// 测试递减
|
||||
Long count3 = redisService.decr(counterKey);
|
||||
assertEquals(5L, count3);
|
||||
|
||||
// 测试SETNX
|
||||
String nxKey = TEST_KEY_PREFIX + "nx";
|
||||
Boolean result1 = redisService.setNx(nxKey, "value1");
|
||||
assertTrue(result1);
|
||||
|
||||
Boolean result2 = redisService.setNx(nxKey, "value2");
|
||||
assertFalse(result2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试Hash类型操作
|
||||
*/
|
||||
@Test
|
||||
void testHashOperations() {
|
||||
String key = TEST_KEY_PREFIX + "hash";
|
||||
|
||||
// 测试单个字段操作
|
||||
redisService.hSet(key, "field1", "value1");
|
||||
Object value = redisService.hGet(key, "field1");
|
||||
assertEquals("value1", value);
|
||||
|
||||
// 测试批量操作
|
||||
Map<String, Object> hash = new HashMap<>();
|
||||
hash.put("field2", "value2");
|
||||
hash.put("field3", "value3");
|
||||
redisService.hMSet(key, hash);
|
||||
|
||||
Map<Object, Object> allFields = redisService.hGetAll(key);
|
||||
assertEquals(3, allFields.size());
|
||||
assertEquals("value1", allFields.get("field1"));
|
||||
assertEquals("value2", allFields.get("field2"));
|
||||
assertEquals("value3", allFields.get("field3"));
|
||||
|
||||
// 测试字段存在性
|
||||
assertTrue(redisService.hExists(key, "field1"));
|
||||
assertFalse(redisService.hExists(key, "nonexistent"));
|
||||
|
||||
// 测试字段递增
|
||||
redisService.hSet(key, "counter", "10");
|
||||
Long newValue = redisService.hIncrBy(key, "counter", 5);
|
||||
assertEquals(15L, newValue);
|
||||
|
||||
// 测试删除字段
|
||||
Long deletedCount = redisService.hDel(key, "field1", "field2");
|
||||
assertEquals(2L, deletedCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试List类型操作
|
||||
*/
|
||||
@Test
|
||||
void testListOperations() {
|
||||
String key = TEST_KEY_PREFIX + "list";
|
||||
|
||||
// 测试左侧推入
|
||||
redisService.lPush(key, "item1", "item2", "item3");
|
||||
Long length = redisService.lLen(key);
|
||||
assertEquals(3L, length);
|
||||
|
||||
// 测试右侧推入
|
||||
redisService.rPush(key, "item4", "item5");
|
||||
length = redisService.lLen(key);
|
||||
assertEquals(5L, length);
|
||||
|
||||
// 测试获取范围
|
||||
var items = redisService.lRange(key, 0, -1);
|
||||
assertEquals(5, items.size());
|
||||
|
||||
// 测试弹出
|
||||
Object leftItem = redisService.lPop(key);
|
||||
assertNotNull(leftItem);
|
||||
|
||||
Object rightItem = redisService.rPop(key);
|
||||
assertNotNull(rightItem);
|
||||
|
||||
length = redisService.lLen(key);
|
||||
assertEquals(3L, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试Set类型操作
|
||||
*/
|
||||
@Test
|
||||
void testSetOperations() {
|
||||
String key = TEST_KEY_PREFIX + "set";
|
||||
|
||||
// 测试添加元素
|
||||
redisService.sAdd(key, "member1", "member2", "member3");
|
||||
Long size = redisService.sCard(key);
|
||||
assertEquals(3L, size);
|
||||
|
||||
// 测试成员存在性
|
||||
assertTrue(redisService.sIsMember(key, "member1"));
|
||||
assertFalse(redisService.sIsMember(key, "nonexistent"));
|
||||
|
||||
// 测试获取所有成员
|
||||
Set<Object> members = redisService.sMembers(key);
|
||||
assertEquals(3, members.size());
|
||||
assertTrue(members.contains("member1"));
|
||||
assertTrue(members.contains("member2"));
|
||||
assertTrue(members.contains("member3"));
|
||||
|
||||
// 测试移除成员
|
||||
Long removedCount = redisService.sRem(key, "member1", "member2");
|
||||
assertEquals(2L, removedCount);
|
||||
|
||||
size = redisService.sCard(key);
|
||||
assertEquals(1L, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试ZSet类型操作
|
||||
*/
|
||||
@Test
|
||||
void testZSetOperations() {
|
||||
String key = TEST_KEY_PREFIX + "zset";
|
||||
|
||||
// 测试添加元素
|
||||
redisService.zAdd(key, "member1", 10.0);
|
||||
redisService.zAdd(key, "member2", 20.0);
|
||||
redisService.zAdd(key, "member3", 15.0);
|
||||
|
||||
Long size = redisService.zCard(key);
|
||||
assertEquals(3L, size);
|
||||
|
||||
// 测试按分数排序获取
|
||||
Set<Object> range = redisService.zRange(key, 0, -1);
|
||||
assertEquals(3, range.size());
|
||||
|
||||
// 测试按分数倒序获取
|
||||
Set<Object> revRange = redisService.zRevRange(key, 0, -1);
|
||||
assertEquals(3, revRange.size());
|
||||
|
||||
// 测试分数递增
|
||||
Double newScore = redisService.zIncrBy(key, "member1", 5.0);
|
||||
assertEquals(15.0, newScore);
|
||||
|
||||
// 测试移除成员
|
||||
Long removedCount = redisService.zRem(key, "member1");
|
||||
assertEquals(1L, removedCount);
|
||||
|
||||
size = redisService.zCard(key);
|
||||
assertEquals(2L, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试通用操作
|
||||
*/
|
||||
@Test
|
||||
void testCommonOperations() {
|
||||
String key = TEST_KEY_PREFIX + "common";
|
||||
|
||||
// 测试键存在性
|
||||
assertFalse(redisService.exists(key));
|
||||
|
||||
redisService.set(key, "value");
|
||||
assertTrue(redisService.exists(key));
|
||||
|
||||
// 测试设置过期时间
|
||||
Boolean expireResult = redisService.expire(key, 10, TimeUnit.SECONDS);
|
||||
assertTrue(expireResult);
|
||||
|
||||
Long ttl = redisService.getExpire(key);
|
||||
assertTrue(ttl > 0 && ttl <= 10);
|
||||
|
||||
// 测试删除键
|
||||
Boolean deleteResult = redisService.delete(key);
|
||||
assertTrue(deleteResult);
|
||||
|
||||
assertFalse(redisService.exists(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试Lua脚本执行
|
||||
*/
|
||||
@Test
|
||||
void testLuaScriptExecution() {
|
||||
String stockKey = TEST_KEY_PREFIX + "stock";
|
||||
|
||||
// 初始化库存
|
||||
redisService.set(stockKey, 100);
|
||||
|
||||
// 测试秒杀脚本
|
||||
Long remainingStock = redisService.executeFlashSaleScript(stockKey, 10);
|
||||
assertEquals(90L, remainingStock);
|
||||
|
||||
// 测试库存不足情况
|
||||
redisService.set(stockKey, 5);
|
||||
Long result = redisService.executeFlashSaleScript(stockKey, 10);
|
||||
assertEquals(-2L, result); // 库存不足
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试分布式锁脚本
|
||||
*/
|
||||
@Test
|
||||
void testDistributedLockScript() {
|
||||
String lockKey = TEST_KEY_PREFIX + "lock";
|
||||
String lockValue = "test_value";
|
||||
|
||||
// 测试获取锁
|
||||
String result1 = redisService.executeLockScript(lockKey, lockValue, 10);
|
||||
assertEquals("OK", result1);
|
||||
|
||||
// 测试重复获取锁
|
||||
String result2 = redisService.executeLockScript(lockKey, "other_value", 10);
|
||||
assertEquals("FAIL", result2);
|
||||
|
||||
// 测试释放锁
|
||||
Long unlockResult = redisService.executeUnlockScript(lockKey, lockValue);
|
||||
assertEquals(1L, unlockResult);
|
||||
|
||||
// 测试释放不存在的锁
|
||||
Long unlockResult2 = redisService.executeUnlockScript(lockKey, lockValue);
|
||||
assertEquals(0L, unlockResult2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理测试数据
|
||||
*/
|
||||
private void cleanupTestData() {
|
||||
// 这里可以添加清理逻辑,删除所有测试键
|
||||
// 由于是测试环境,可以使用通配符删除
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user