测试与混沌测试
基于 rts-server-golang sim/、cmd/ 目录解析 关键词: 确定性测试, 混沌注入, Fuzzing, 回归测试, 网络模拟
概述
RTS 游戏服务器的测试有三个特殊难点:
- 确定性:同样的输入必须产生同样的结果
- 网络不可靠:UDP 会丢包、乱序、延迟
- 时间相关:tick 推进依赖时间流逝
本项目通过三类测试覆盖这些场景。
确定性测试
为什么确定性测试特殊
普通测试:
go
got := Add(2, 3)
if got != 5 { t.Error(...) } // 结果确定确定性测试需要多次运行,验证结果一致:
go
hashes := make([]uint64, 100)
for i := range hashes {
w := setupTestWorld()
Step(w, cmds) // 同初始状态、同命令
hashes[i] = Hash(w)
}
allSame(hashes) // 100次结果必须完全相同TestHashDeterminism
验证:相同初始状态 + 相同命令序列 → 100 次 hash 完全一致。
go
func TestHashDeterminism(t *testing.T) {
hashes := make([]uint64, 100)
for i := range hashes {
w := setupTestWorld()
cmds := []Cmd{
{Player: 0, Op: CmdMove, UnitID: 1, TargetPos: fixed.VInt(50, 50)},
{Player: 1, Op: CmdAttack, UnitID: 4, TargetID: 1},
}
for tick := 0; tick < 200; tick++ {
if tick == 0 { Step(w, cmds) } else { Step(w, nil) }
}
hashes[i] = Hash(w)
}
for i := 1; i < len(hashes); i++ {
if hashes[i] != hashes[0] {
t.Fatalf("hash mismatch at run %d", i)
}
}
}失败场景:如果 Step() 中用了浮点数、随机数、或遍历顺序不确定,hash 会发散。
TestSnapshotRoundTrip
验证:Marshal → Unmarshal → 继续跑,结果仍然一致。
go
w := setupTestWorld()
Step(w, cmds)
data := Marshal(w)
w2, _ := Unmarshal(data)
// hash 必须完全一致
if Hash(w) != Hash(w2) { t.Fatal("hash mismatch after roundtrip") }
// 继续跑 100 tick,hash 仍然一致
for i := 0; i < 100; i++ {
Step(w, nil)
Step(w2, nil)
}
if Hash(w) != Hash(w2) { t.Fatal("hash diverged") }TestFixedDeterminism
验证固定点数学的确定性:
go
func TestDeterminism(t *testing.T) {
var results [100]int32
for i := range results {
a := FromFloat64(3.14159)
b := FromFloat64(2.71828)
c := a.Mul(b).Add(Sqrt(a)).Sub(b.Div(a))
results[i] = (c + Sin(a).Mul(Cos(b))).Raw()
}
for i := 1; i < len(results); i++ {
if results[i] != results[0] {
t.Fatalf("determinism broken: run %d", i)
}
}
}固定点数学测试
基本运算
go
func TestMulDiv(t *testing.T) {
a := FromFloat64(3.5)
b := FromFloat64(2.0)
mul := a.Mul(b) // 3.5 * 2.0 = 7.0
div := a.Div(b) // 3.5 / 2.0 = 1.75
}Sqrt — 二分查找
go
func TestSqrt(t *testing.T) {
// 手动构造测试用例
// 2 的平方根 ≈ 1.4142,误差容忍 0.01
got := Sqrt(FromFloat64(2)).ToFloat64()
if math.Abs(got-1.4142) > 0.01 { t.Error(...) }
}Vec2 — 距离计算
go
func TestVec2MoveToward(t *testing.T) {
from := VInt(0, 0)
target := VInt(10, 0)
maxDist := FromInt(3)
result := MoveToward(from, target, maxDist)
// 3/10 的距离 → x = 3
if result.X.ToFloat64() != 3.0 { t.Error(...) }
// 距离不足时直接到目标
from2 := V(FromFloat64(9.5), FromInt(0))
result2 := MoveToward(from2, target, maxDist)
if result2.X != target.X { t.Error(...) }
}混沌测试 (chaos-shim)
chaos-shim 是独立的 UDP 代理,夹在客户端和服务端之间,注入网络故障:
客户端 → chaos-shim(:9002) → 服务端(:9000)
↑ 注入丢包/延迟/乱序启动方式
bash
./chaos-shim \
-listen :9002 \ # 代理监听端口
-forward 127.0.0.1:9000 \ # 真实服务端
-drop 0.05 \ # 5% 丢包率
-delay 50 \ # 基础延迟 50ms
-jitter 20 \ # 抖动 ±20ms注入逻辑
go
chaosForward := func(data []byte, dst *net.UDPAddr) {
// 1. 丢包
if rng.Float64() < *dropPct {
dropped++
return
}
// 2. 延迟 + 抖动
delay := time.Duration(*delayMs) * time.Millisecond
delay += time.Duration(rng.Intn(*jitterMs*2)-*jitterMs) * time.Millisecond
// 异步发送
go func() {
time.Sleep(delay)
conn.WriteToUDP(data, dst)
}()
}验证内容
| 故障类型 | 验证 |
|---|---|
| 丢包 5% | 重传机制正常工作 |
| 延迟 50±20ms | AdaptiveN 能适应变化 |
| 抖动 | RTT 估算准确,RTO 合理 |
| 组合 | 断线重连能成功恢复 |
回归测试建议
对于实际项目,建议添加:
bash
# 确定性回归
go test -count=100 ./sim/...
# 对抗模式混沌测试
./chaos-shim -drop=0.1 -delay=100 &
./fake-client -players=2 -ticks=1000
./server -tick-rate=20
# 并发安全
go test -race ./...相关
- 项目源码: rts-server-golang
/internal/sim/,/cmd/chaos-shim/ - 上篇: 06_服务端架构