bindbox-game/internal/service/livestream/discrete_random_test.go
2026-02-03 17:44:02 +08:00

218 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package livestream
import (
"encoding/json"
"testing"
"bindbox-game/internal/repository/mysql/model"
)
// TestDiscreteRandomDistribution 测试随机离散分布的均匀性
func TestDiscreteRandomDistribution(t *testing.T) {
// 模拟实际奖品配置
jsonData := `[{"id":58,"name":"Nu高达","weight":100},{"id":59,"name":"NT高达","weight":100},{"id":60,"name":"魔礼青","weight":100},{"id":61,"name":"维达尔","weight":100},{"id":62,"name":"玉衡星6号","weight":400},{"id":63,"name":"马克兔","weight":600},{"id":64,"name":"00高达","weight":700},{"id":65,"name":"SD随机款","weight":1100},{"id":66,"name":"兰博基尼","weight":3000},{"id":67,"name":"高达徽章","weight":28000},{"id":68,"name":"冰箱贴","weight":65800}]`
var prizes []*model.LivestreamPrizes
if err := json.Unmarshal([]byte(jsonData), &prizes); err != nil {
t.Fatalf("解析奖品数据失败: %v", err)
}
// 计算总权重
var totalWeight int32
for _, p := range prizes {
totalWeight += p.Weight
}
t.Logf("总权重: %d", totalWeight)
// 生成随机离散位置
state, err := GenerateDiscretePositions(1, prizes)
if err != nil {
t.Fatalf("生成随机离散位置失败: %v", err)
}
// 模拟10000次抽奖
simulateCount := 10000
results := make(map[int64]int)
for i := 0; i < simulateCount; i++ {
// 模拟随机值
randValue := int32(i % int(totalWeight)) // 简化为顺序遍历实际用crypto/rand
// 查找对应的奖品
for prizeID, positions := range state.PrizePosMap {
for _, pos := range positions {
if pos == randValue {
results[prizeID]++
break
}
}
}
}
// 创建奖品ID到名称的映射
prizeMap := make(map[int64]string)
for _, p := range prizes {
prizeMap[p.ID] = p.Name
}
// 输出统计结果
t.Log("\n========== 随机离散分布测试结果 ==========")
for _, p := range prizes {
count := results[p.ID]
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)
}
}
// TestContinuousVsDiscrete 对比连续区间和随机离散
func TestContinuousVsDiscrete(t *testing.T) {
// 简化奖品配置
prizes := []*model.LivestreamPrizes{
{ID: 1, Name: "大奖", Weight: 100},
{ID: 2, Name: "小奖A", Weight: 3000},
{ID: 3, Name: "小奖B", Weight: 3000},
{ID: 4, Name: "冰箱贴", Weight: 65800},
}
totalWeight := int32(100 + 3000 + 3000 + 65800)
t.Logf("\n========== 连续区间 vs 随机离散对比 ==========")
t.Logf("总权重: %d", totalWeight)
t.Log("")
// 连续区间模拟
t.Log("【连续区间方法】")
t.Log("大奖区间: [0, 99] (0.1%)")
t.Log("小奖A区间: [100, 3099] (3%)")
t.Log("小奖B区间: [3100, 6099] (3%)")
t.Log("冰箱贴区间: [6100, 71899] (65.8%)")
t.Log("")
// 随机离散模拟
state, err := GenerateDiscretePositions(1, prizes)
if err != nil {
t.Fatalf("生成随机离散位置失败: %v", err)
}
t.Log("【随机离散方法】")
t.Logf("大奖的随机位置数: %d", len(state.PrizePosMap[1]))
t.Logf("小奖A的随机位置数: %d", len(state.PrizePosMap[2]))
t.Logf("小奖B的随机位置数: %d", len(state.PrizePosMap[3]))
t.Logf("冰箱贴的随机位置数: %d", len(state.PrizePosMap[4]))
// 验证位置分布
t.Log("\n位置分布示例前10个:")
if positions, ok := state.PrizePosMap[1]; ok && len(positions) > 0 {
sampleSize := 10
if len(positions) < sampleSize {
sampleSize = len(positions)
}
t.Logf("大奖位置: %v", positions[:sampleSize])
}
if positions, ok := state.PrizePosMap[4]; ok && len(positions) > 0 {
sampleSize := 10
if len(positions) < sampleSize {
sampleSize = len(positions)
}
t.Logf("冰箱贴位置: %v", positions[:sampleSize])
}
}
// TestEdgeCases 测试边界情况
func TestEdgeCases(t *testing.T) {
t.Run("空奖品列表", func(t *testing.T) {
_, err := GenerateDiscretePositions(1, []*model.LivestreamPrizes{})
if err == nil {
t.Error("应该返回错误")
}
})
t.Run("零权重", func(t *testing.T) {
prizes := []*model.LivestreamPrizes{
{ID: 1, Name: "奖品", Weight: 0},
}
_, err := GenerateDiscretePositions(1, prizes)
if err == nil {
t.Error("应该返回错误")
}
})
t.Run("单个奖品", func(t *testing.T) {
prizes := []*model.LivestreamPrizes{
{ID: 1, Name: "唯一奖品", Weight: 1000},
}
state, err := GenerateDiscretePositions(1, prizes)
if err != nil {
t.Fatalf("不应该出错: %v", err)
}
if len(state.PrizePosMap[1]) != 1000 {
t.Errorf("位置数量错误期望1000实际%d", len(state.PrizePosMap[1]))
}
})
}
// TestPositionUniqueness 测试位置的唯一性
func TestPositionUniqueness(t *testing.T) {
prizes := []*model.LivestreamPrizes{
{ID: 1, Name: "大奖", Weight: 100},
{ID: 2, Name: "小奖", Weight: 3000},
{ID: 3, Name: "冰箱贴", Weight: 65800},
}
state, err := GenerateDiscretePositions(1, prizes)
if err != nil {
t.Fatalf("生成失败: %v", err)
}
// 收集所有位置
allPositions := make(map[int32]int64)
for prizeID, positions := range state.PrizePosMap {
for _, pos := range positions {
if existingPrizeID, exists := allPositions[pos]; exists {
t.Errorf("位置 %d 被多个奖品占用: 奖品%d 和 奖品%d", pos, existingPrizeID, prizeID)
}
allPositions[pos] = prizeID
}
}
expectedCount := 100 + 3000 + 65800
if len(allPositions) != expectedCount {
t.Errorf("位置总数错误,期望%d实际%d", expectedCount, len(allPositions))
}
t.Logf("位置唯一性验证通过: %d 个唯一位置", len(allPositions))
}
// BenchmarkDiscreteVsContinuous 性能对比测试
func BenchmarkDiscreteGeneration(b *testing.B) {
prizes := []*model.LivestreamPrizes{
{ID: 1, Name: "大奖", Weight: 100},
{ID: 2, Name: "小奖A", Weight: 3000},
{ID: 3, Name: "小奖B", Weight: 3000},
{ID: 4, Name: "冰箱贴", Weight: 65800},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := GenerateDiscretePositions(int64(i), prizes)
if err != nil {
b.Fatal(err)
}
}
}
// 打印结果辅助函数
func printResults(t *testing.T, results map[int64]int, prizes []*model.LivestreamPrizes, simulateCount int) {
t.Log("\n========== 测试结果 ==========")
for _, p := range prizes {
count := results[p.ID]
t.Logf("%-20s 中奖次数: %d (%.2f%%)", p.Name, count, float64(count)/float64(simulateCount)*100)
}
}