242 lines
6.4 KiB
Go
242 lines
6.4 KiB
Go
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)
|
||
}
|