Go单元测试应通过接口抽象和依赖注入隔离外部依赖,用手工mock、httptest.Server或内存SQLite替代硬编码调用,避免gomock和httpmock等易失效方案。

用接口抽象 + 依赖注入替代硬编码调用
Go 的单元测试无法直接“打桩”第三方 HTTP 客户端或数据库驱动,核心解法是提前把外部依赖抽象成接口,并通过构造函数或方法参数注入。这样测试时就能传入 mock 实现,彻底隔离真实服务。
常见错误是直接在业务逻辑里写 http.DefaultClient.Do() 或 sql.Open(),导致测试必须连网或启数据库。正确做法是定义接口并让结构体持有一个该接口字段:
type PaymentService interface {
Charge(amount float64, cardToken string) error
}
type OrderProcessor struct {
payment PaymentService // 依赖接口,而非具体实现
}
测试时只需提供一个满足该接口的 fake 结构体,无需任何第三方库。
手动实现 mock 接口比用 gomock 更轻量且可控
gomock 生成代码冗长、难调试,且容易因接口变更导致编译失败。多数场景下,手写 mock 更快、更直观,也更容易覆盖边界逻辑(如模拟超时、空响应、特定错误)。
立即学习“go语言免费学习笔记(深入)”;
例如模拟一个失败的支付服务:
type failingPaymentService struct{}
func (f *failingPaymentService) Charge(amount float64, cardToken string) error {
return fmt.Errorf("payment declined: %s", cardToken)
}
使用时直接传入:processor := &OrderProcessor{payment: &failingPaymentService{}}。这种写法清晰暴露了行为契约,也方便在测试中组合不同返回路径。
- 不要为每个方法都写完整 mock —— 只实现当前测试用到的方法即可
- mock 方法内部避免调用真实网络或磁盘,否则就不是单元测试了
- 若需验证调用次数或参数,可在 mock 中加字段记录,比如
calledWithAmount float64
HTTP 依赖优先用 httptest.Server 而非 httpmock
当业务代码依赖 http.Client 调用外部 API 时,最可靠的方式是启动一个本地 httptest.Server,让它返回预设响应。它完全走真实 HTTP 栈,能捕获 client 配置问题(如 timeout、header 设置),而 httpmock 这类纯拦截方案会绕过 Transport 层,掩盖配置缺陷。
示例:
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
}))
defer srv.Close()
client := &http.Client{Timeout: time.Second}
resp, _ := client.Get(srv.URL + "/pay") // 真实发起请求
注意:srv.URL 是可访问地址,srv.Close() 必须调用,否则端口泄漏。
数据库依赖用内存 SQLite 或 sqlmock(谨慎)
真实 PostgreSQL/MySQL 启动成本高、状态难清理;纯内存 SQLite(sqlite3://file::memory:?cache=shared)适合多数 CRUD 场景,支持事务、外键,且每个 test case 可重建 schema。
sqlmock 虽能断言 SQL 语句,但容易让测试过度耦合实现细节(比如“必须调用 WHERE id = ?”),一旦重构 SQL 就要改测试。更健壮的做法是:用内存 DB 执行真实查询,再校验结果是否符合预期。
关键点:
- 所有 DB 初始化(
db.Exec("CREATE TABLE..."))放在TestXxx函数开头,不复用连接 - 避免在
init()或包级变量中打开 DB,否则并发测试会冲突 - 如果必须验证 SQL,确保只断言必要部分(如表名、WHERE 字段),忽略排序、括号格式等无关差异
真实依赖越少、抽象越早、mock 越简单,测试才越稳定。很多人卡在“不知道该 mock 哪一层”,其实答案很直接:只要不是当前包定义的类型,就该被替换。










