package livestream import ( "testing" "bindbox-game/internal/repository/mysql/model" ) // TestDrawWithDiscreteIntegration 集成测试:完整的随机离散抽奖流程 func TestDrawWithDiscreteIntegration(t *testing.T) { // 创建service实例(简化测试,不依赖真实数据库) s := &service{ discreteCache: NewActivityDiscreteCache(), } // 1. 配置奖品 prizes := []*model.LivestreamPrizes{ {ID: 58, Name: "Nu高达", Weight: 100, Remaining: -1}, {ID: 59, Name: "NT高达", Weight: 100, Remaining: -1}, {ID: 60, Name: "魔礼青", Weight: 100, Remaining: -1}, {ID: 61, Name: "维达尔", Weight: 100, Remaining: -1}, {ID: 62, Name: "玉衡星6号", Weight: 400, Remaining: -1}, {ID: 63, Name: "马克兔", Weight: 600, Remaining: -1}, {ID: 64, Name: "00高达", Weight: 700, Remaining: -1}, {ID: 65, Name: "SD随机款", Weight: 1100, Remaining: -1}, {ID: 66, Name: "兰博基尼", Weight: 3000, Remaining: -1}, {ID: 67, Name: "高达徽章", Weight: 28000, Remaining: -1}, {ID: 68, Name: "冰箱贴", Weight: 65800, Remaining: -1}, } activityID := int64(1) // 2. 生成随机离散位置 state, err := GenerateDiscretePositions(activityID, prizes) if err != nil { t.Fatalf("生成随机离散位置失败: %v", err) } // 3. 设置到缓存 s.discreteCache.Set(state) t.Logf("总权重: %d", state.TotalWeight) // 4. 模拟多次抽奖 simulateCount := 1000 results := make(map[int64]int) for i := 0; i < simulateCount; i++ { // 模拟随机值生成(实际用crypto/rand) randValue := int32(i % int(state.TotalWeight)) // 查找对应的奖品 prizeMap := make(map[int64]*model.LivestreamPrizes) for _, p := range prizes { prizeMap[p.ID] = p } // 二分查找 found := false for prizeID, positions := range state.PrizePosMap { for _, pos := range positions { if pos == randValue { results[prizeID]++ found = true break } } if found { break } } } // 5. 统计结果 t.Log("\n========== 随机离散抽奖统计结果 ==========") t.Logf("模拟次数: %d", simulateCount) t.Log("") totalWeight := int32(0) for _, p := range prizes { totalWeight += p.Weight } totalCount := 0 for _, p := range prizes { count := results[p.ID] totalCount += count theoryProb := float64(p.Weight) / float64(totalWeight) * 100 actualProb := float64(count) / float64(simulateCount) * 100 diff := actualProb - theoryProb t.Logf("%-20s 权重:%6d 理论:%6.2f%% 实际:%6.2f%% 偏差:%+6.2f%% 次数:%5d", p.Name, p.Weight, theoryProb, actualProb, diff, count) } t.Logf("\n总计中奖: %d", totalCount) // 6. 验证概率偏差(允许的误差范围:±2%) t.Run("概率偏差检查", func(t *testing.T) { for _, p := range prizes { count := results[p.ID] theoryProb := float64(p.Weight) / float64(totalWeight) * 100 actualProb := float64(count) / float64(simulateCount) * 100 diff := actualProb - theoryProb // 大奖(权重100)允许更大偏差 threshold := 2.0 if p.Weight <= 100 { threshold = 5.0 // 大奖允许5%偏差 } if diff > threshold || diff < -threshold { t.Errorf("%s 概率偏差过大: 理论%.2f%% 实际%.2f%% 偏差%.2f%% (阈值±%.1f%%)", p.Name, theoryProb, actualProb, diff, threshold) } } }) } // TestDrawVsOriginal 对比新旧方案 func TestDrawVsOriginal(t *testing.T) { prizes := []*model.LivestreamPrizes{ {ID: 1, Name: "大奖", Weight: 100, Remaining: -1}, {ID: 2, Name: "中奖", Weight: 3000, Remaining: -1}, {ID: 3, Name: "小奖", Weight: 3000, Remaining: -1}, {ID: 4, Name: "冰箱贴", Weight: 65800, Remaining: -1}, } activityID := int64(999) // 原始连续区间方案 t.Log("\n========== 方案对比 ==========") t.Log("【原始连续区间方案】") t.Log("大奖区间: [0, 99] (0.15%)") t.Log("中奖区间: [100, 3099] (4.48%)") t.Log("小奖区间: [3100, 6099] (4.48%)") t.Log("冰箱贴区间: [6100, 71899] (97.02%)") t.Log("") // 随机离散方案 state, err := GenerateDiscretePositions(activityID, prizes) if err != nil { t.Fatalf("生成失败: %v", err) } t.Log("【随机离散方案】") for _, p := range prizes { positions := state.PrizePosMap[p.ID] t.Logf("%s: %d个随机位置", p.Name, len(positions)) } // 位置分布示例 t.Log("\n【位置分布示例】") if pos, ok := state.PrizePosMap[1]; ok && len(pos) > 0 { sample := make([]int32, 0, 10) for i := 0; i < 10 && i < len(pos); i++ { sample = append(sample, pos[i]) } t.Logf("大奖前10个位置: %v", sample) } if pos, ok := state.PrizePosMap[4]; ok && len(pos) > 0 { sample := make([]int32, 0, 10) for i := 0; i < 10 && i < len(pos); i++ { sample = append(sample, pos[i]) } t.Logf("冰箱贴前10个位置: %v", sample) } } // TestCacheOperations 测试缓存操作 func TestCacheOperations(t *testing.T) { cache := NewActivityDiscreteCache() // 创建测试状态 state := &ActivityDiscreteState{ ActivityID: 1, TotalWeight: 1000, PrizePosMap: map[int64][]int32{ 1: {0, 10, 20, 30, 40}, 2: {5, 15, 25, 35, 45}, }, } // 测试Set cache.Set(state) // 测试Get got, ok := cache.Get(1) if !ok { t.Fatal("应该能找到缓存") } if got.ActivityID != 1 { t.Errorf("ActivityID不匹配: 期望1, 实际%d", got.ActivityID) } // 测试Delete cache.Delete(1) _, ok = cache.Get(1) if ok { t.Error("删除后应该找不到") } t.Log("缓存操作测试通过") } // TestSelectPrizeByDiscreteWithList 测试带奖品列表的选择 func TestSelectPrizeByDiscreteWithList(t *testing.T) { prizes := []*model.LivestreamPrizes{ {ID: 1, Name: "大奖", Weight: 100, Remaining: -1}, {ID: 2, Name: "小奖", Weight: 3000, Remaining: -1}, } state, err := GenerateDiscretePositions(1, prizes) if err != nil { t.Fatalf("生成失败: %v", err) } // 测试有效随机值 for randValue := int32(0); randValue < state.TotalWeight; randValue++ { prize, _, err := SelectPrizeByDiscreteWithList(state, prizes, randValue) if err != nil { t.Errorf("随机值%d查找失败: %v", randValue, err) continue } if prize == nil { t.Errorf("随机值%d返回空奖品", randValue) continue } } // 测试边界值 t.Run("边界值测试", func(t *testing.T) { // 超出范围 _, _, err := SelectPrizeByDiscreteWithList(state, prizes, state.TotalWeight) if err == nil { t.Error("超出范围应该返回错误") } }) t.Logf("成功测试了 %d 个随机值", state.TotalWeight) }