集成测试应只 mock 直接依赖、保留真实链路关键环节,HTTP 依赖首选 httptest.NewServer 启动真实监听服务并注入 server.URL;gRPC 需抽象接口后 mock,数据库和 Redis 应用轻量真实实例;测试须物理隔离、加 integration 标签与 -short 守卫。

集成测试里模拟外部依赖,不是“把所有下游都 mock 掉”,而是只 mock 直接依赖、保留真实链路关键环节——否则测的不是系统行为,是 mock 行为本身。
httptest.NewServer 是 HTTP 依赖模拟的默认选择
只要被测服务调用的是标准 http.Client,就该用 httptest.NewServer 起一个真实监听的临时服务,而不是手写 handler 或硬编码地址。
- 它自动分配空闲端口,避免并发测试时端口冲突;
server.URL可直接注入客户端配置,无需解析 host/port - 别用
httptest.NewUnstartedServer:它不启动 listener,得手动.Start(),且容易漏掉清理导致 goroutine 泄漏 - 每个测试用例必须
defer server.Close(),否则端口和 goroutine 累积,CI 上跑几十个测试后会卡死 - handler 内不要
panic,用w.WriteHeader()+ 显式错误响应;如需校验请求头或 body,就在 handler 里读r.Body并断言,别指望外部工具补位
gRPC 依赖必须抽象成接口再 mock
直接 mock protobuf 生成的 stub 几乎不可维护——字段变更、context 传递、error 类型(如 status.Error(codes.NotFound, ""))稍有不一致,中间件就 panic。
- 先定义业务接口,例如
type ProductClient interface { Get(ctx context.Context, req *GetRequest) (*GetResponse, error) } - 真实实现包装 gRPC client;测试时注入手写 mock 或
gomock生成的 mock,返回预设值或特定 error - mock 的 error 必须是
status.Error类型,不能是fmt.Errorf,否则 grpc-go 中间件(如 recovery、logging)可能崩溃 - 如果用
gomock,ctrl := gomock.NewController(t)后必须立刻defer ctrl.Finish(),漏掉这行会导致 EXPECT 未触发却通过测试,或在 Finish 时 panic 报 “expected call never happened”
数据库和 Redis 要用轻量真实实例,不是纯 mock
SQL 查询逻辑、事务边界、索引行为、Redis pipeline 响应顺序……这些没法靠 sqlmock 或 hand-written map 模拟到位。该起真实轻量实例时就得起。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 本地开发/CI 用
docker-compose up -d postgres或miniredis/v2启动实例,比 mock 更贴近生产 - 测试前执行
TRUNCATE TABLE或用事务 +Rollback隔离数据,别依赖全局清库脚本 - 连接字符串从环境变量或测试辅助函数动态生成(如
fmt.Sprintf("host=localhost port=%d dbname=test sslmode=disable", port)),避免写死 - 用
t.Cleanup()关闭 DB 连接、停 redis 容器,否则资源残留影响后续测试
集成测试必须和单元测试物理隔离
混在一起跑,等于拿单元测试的快节奏去承受集成测试的慢与不稳定——失败日志里根本分不清是逻辑 bug 还是端口被占。
- 命名统一加
TestIntegration*前缀,配合go test -run=TestIntegration单独执行 - 更稳妥的做法是加构建标签:
//go:build integration,运行时用go test -tags=integration - CI 中明确分离阶段:单元测试走 fast path,集成测试单独 job + 更长 timeout + 专用 runner(带 Docker)
- 加
-short守卫:在测试开头写if testing.Short() { t.Skip("skipping integration test") },本地快速验证时跳过
最容易被忽略的是 error 类型一致性——mock 返回的 error 和真实下游返回的 error 类型不匹配,中间件一 catch 就 panic,但这个 panic 不会出现在 handler 日志里,而是在 recover 阶段静默吞掉,最终表现为“请求没响应”或“状态码不对”。这点必须手工核对,不能靠 mockgen 自动生成。

















