外观
测试与性能基准
Phase 07 — Engineering · 知识点 05–07:Unit Test · Integration Test · Benchmark
1. 学习目标
完成本知识点后,你应该能够:
- 编写 Unit Test 覆盖 service 层和纯函数逻辑
- 使用 table-driven tests 组织多组用例
- 编写 Integration Test 验证 HTTP API 与数据库交互
- 使用
httptest测试 Handler 而无需启动真实端口 - 编写 Benchmark 评估 JSON 序列化、Hub 广播等热点路径
- 运行
go test -race检测 WebSocket Hub 竞态条件
2. 为什么需要
数字孪生后端涉及 AGV 坐标校验、告警规则、Room 路由——逻辑错误会导致 Three.js 显示错误或漏报告警。没有测试,每次重构 WebSocket 或 Pipeline 都是赌博。
Go 内置测试框架,无需引入 Jest 级重型依赖。Unit Test 保证逻辑正确,Integration Test 保证组件协作,Benchmark 在优化 Concurrent Connections 时有数据支撑。
3. 核心概念
3.1 Unit Test(单元测试)
- 测试单个函数/方法,依赖用 Mock 或 Fake 替代
- 文件命名:
xxx_test.go,函数:func TestXxx(t *testing.T) - 快、稳定、无外部网络/DB(理想情况)
3.2 Integration Test(集成测试)
- 测试多个组件协作:HTTP → service → PostgreSQL
- 可用 Docker 启动测试 DB,或
testcontainers-go - 本路线可先用内存 SQLite / 内嵌 Postgres 简化
3.3 Benchmark(基准测试)
- 函数:
func BenchmarkXxx(b *testing.B) - 测量 ns/op、内存分配
- 用于对比优化前后 JSON、Hub 广播性能
3.4 测试命令
bash
go test ./... # 所有包
go test -v ./internal/service/ # 详细输出
go test -race ./... # 竞态检测
go test -bench=. ./internal/ws/ # 基准测试
go test -cover ./... # 覆盖率4. 基础语法
4.1 Table-driven Unit Test
go
// internal/service/alert_test.go
package service
import "testing"
func TestLowBatteryAlert(t *testing.T) {
tests := []struct {
name string
battery int
want bool
}{
{"normal", 50, false},
{"low", 15, true},
{"boundary", 20, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ShouldAlertLowBattery(tt.battery, 20)
if got != tt.want {
t.Errorf("battery=%d: got %v, want %v", tt.battery, got, tt.want)
}
})
}
}4.2 httptest Handler 测试
go
func TestGetDeviceSnapshot(t *testing.T) {
svc := service.NewDeviceService(fakeRepo{})
mux := http.NewServeMux()
handler.RegisterDeviceRoutes(mux, svc)
req := httptest.NewRequest(http.MethodGet, "/api/v1/devices/AGV-001", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status: got %d", rec.Code)
}
var body map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
}4.3 Fake Repository
go
type fakeRepo struct{}
func (f fakeRepo) GetByID(ctx context.Context, id string) (*domain.Device, error) {
return &domain.Device{ID: id, X: 10, Y: 20}, nil
}4.4 Benchmark
go
func BenchmarkBroadcast(b *testing.B) {
hub := ws.NewHub()
go hub.Run()
// 注册 N 个 mock clients...
payload := []byte(`{"type":"agv.position","deviceId":"AGV-001"}`)
b.ResetTimer()
for i := 0; i < b.N; i++ {
hub.Broadcast(payload)
}
}4.5 Race 检测
WebSocket Hub 的 register/unregister/broadcast 必须在 -race 下无报警:
bash
go test -race ./internal/ws/ -count=15. 代码解析
t.Run 子测试:失败时定位到具体 case 名称,类似 Jest 的 it.each。
httptest.NewRecorder:内存 ResponseWriter,不占用端口,测试并行安全。
b.ResetTimer():排除 setup 时间,只计循环体。
Fake vs Mock:Fake 是简化实现;Mock 通常指 testify/mock 等代码生成,本路线 Fake 足够。
6. JavaScript / TypeScript 对比
| 概念 | Jest / Vitest | Go testing |
|---|---|---|
| 文件 | *.test.ts | *_test.go |
| 断言 | expect(x).toBe(y) | if got != want { t.Error(...) } |
| 表驱动 | test.each | []struct{...} + t.Run |
| HTTP 测试 | supertest | httptest |
| 覆盖率 | --coverage | -cover |
| 性能 | 无内置 | -bench 内置 |
Go 测试与源码同包或 _test 外部测试包(package service_test 测公开 API)。
7. 常见错误
错误 1:测试依赖真实生产 DB
测试应可重复、隔离,使用内存实现或测试容器。
错误 2:不测边界条件
电量 20%、坐标 0、空 room 等边界是 bug 温床。
错误 3:忽略 -race
Hub 偶发 panic 难以复现,race detector 能提前发现。
错误 4:Benchmark 在 CI 中不设 -benchtime
默认 1s 可能波动大,关注趋势而非绝对值。
错误 5:Integration Test 与 Unit Test 混在同一文件无 build tag
可用 //go:build integration 分离慢测试。
8. 实际应用
Project 03 测试清单
| 层级 | 测试内容 |
|---|---|
| service | 低电量告警、坐标边界、Room 路由逻辑 |
| handler | GET snapshot 200/404,非法 JSON 400 |
| ws | Hub 注册/注销、Broadcast 不 panic、race 通过 |
| benchmark | json.Marshal(AGVPositionMsg)、Broadcast 100 clients |
CI 最小命令(Phase 08 详述):
bash
go vet ./...
go test ./...
go test -race ./...9. 深入理解
9.1 外部测试包
package handler_test 只能测导出符号,模拟真实调用方视角。
9.2 TestMain
go
func TestMain(m *testing.M) {
setup()
code := m.Run()
teardown()
os.Exit(code)
}Integration Test 共享 DB setup/teardown。
9.3 Fuzzing(Go 1.18+)
go
func FuzzParseCoordinate(f *testing.F) {
f.Add("12.5")
f.Fuzz(func(t *testing.T, s string) { ... })
}可选扩展,用于解析客户端消息的健壮性。
10. 练习
详细练习见
exercises/phase-07-engineering/02-testing-benchmark.md。
Level 1 — 基础
练习 1.1:为 ShouldAlertLowBattery 写 table-driven test。
练习 1.2:用 httptest 测试 GET /health 返回 200。
Level 2 — 应用
练习 2.1:Fake Repository + service 层 GetDevice 测试。
练习 2.2:Hub register/unregister 后 clients 数量断言。
Level 3 — 综合
练习 3.1:Integration test:POST 创建设备 → GET 验证。
练习 3.2:go test -race ./internal/ws/ 修复竞态。
Level 4 — 项目实践
练习 4.1:Project 03 核心路径测试覆盖率 > 60%,benchmark 报告 JSON 序列化 ns/op。
11. 学习检查
*_test.go文件如何与源码关联?- table-driven test 的优势是什么?
httptest与真实ListenAndServe测试有何不同?- 何时用 Integration Test 而非 Unit Test?
-race解决什么问题?
12. 下一步
| 已完成 | 下一知识点 | 关系 |
|---|---|---|
| Unit · Integration · Benchmark | Docker · Docker Compose | 测试通过 → 容器化交付 |
下一步学习 Docker 镜像构建与 Compose 多服务编排,实现本地一键启动 Go + PostgreSQL + Redis。