Go测试需显式检查error值或用testify/assert断言,错误应通过errors.Is验证类型,panic需defer+recover测试,mock依赖须基于interface,且须覆盖正常路径。

Go 的测试本身不提供“捕获错误”的运行时拦截机制(比如 Python 的 assertRaises),你不能靠包装函数调用来“抓” panic 或 error 返回值——必须显式检查返回的 error 值,或用 testify/assert 等辅助库做语义断言。
直接检查函数返回的 error 是否为 nil 或匹配预期
这是最常见、最 Go-idiomatic 的方式。Go 函数通常把 error 作为最后一个返回值,测试中应显式接收并验证它:
func TestDivide(t *testing.T) {
result, err := Divide(10, 0)
if err == nil {
t.Fatal("expected error, got nil")
}
if !errors.Is(err, ErrDivideByZero) {
t.Errorf("expected %v, got %v", ErrDivideByZero, err)
}
if result != 0 {
t.Error("result should be 0 when error occurs")
}
}
-
errors.Is()比==更安全,能正确处理包装错误(如fmt.Errorf("wrap: %w", err)) - 不要只检查
err != nil就结束——要确认错误类型/消息是否符合业务预期 - 即使出错,也要检查其他返回值是否按约定置零(Go 中未赋值变量有零值,但有些逻辑可能意外覆盖)
用 testify/assert 简化 error 断言逻辑
原生 t.Error 写多了容易冗长。testify/assert 提供了更紧凑、可读性更强的写法:
import "github.com/stretchr/testify/assert"
func TestDivideWithAssert(t *testing.T) {
result, err := Divide(10, 0)
assert.ErrorIs(t, err, ErrDivideByZero)
assert.Zero(t, result)
}
-
assert.ErrorIs()对应errors.Is(),推荐用于自定义错误变量 -
assert.ErrorContains()适合检查错误消息子串(例如日志调试时),但不推荐用于生产断言——消息易变,破坏稳定性 - 注意:
assert失败不会终止当前测试函数(只是记录失败),需配合require包做“断言失败即停止”:如require.ErrorIs(t, err, ...)
模拟外部依赖错误(如 HTTP 调用、DB 查询)
真实错误往往来自 I/O,测试时不能真发请求。关键是在接口层面解耦,用 interface + mock 实现可控错误注入:
立即学习“go语言免费学习笔记(深入)”;
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
func FetchData(client HTTPClient, url string) ([]byte, error) {
resp, err := client.Do(http.NewRequest("GET", url, nil))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// 测试中传入 mock client:
type mockHTTPClient struct{ err error }
func (m mockHTTPClient) Do(*http.Request) (*http.Response, error) {
return nil, m.err
}
func TestFetchData_NetworkError(t *testing.T) {
data, err := FetchData(mockHTTPClient{err: errors.New("timeout")}, "https://api.example.com")
assert.Error(t, err)
assert.Nil(t, data)
}
- 不要 mock 具体类型(如
*http.Client),而是抽象出 interface;否则无法替换行为 - mock 实现越简单越好,只返回预设 error 或固定数据,避免在测试里引入新逻辑
- 如果依赖是全局变量(如
http.DefaultClient),需在测试前用defer恢复原值,防止污染其他测试
panic 错误不能被普通 error 断言捕获,需用 recover 显式测试
只有当函数内部 panic,且你**有意测试该 panic 行为**时,才需要手动 recover。这不是常规错误处理路径,而是边界测试:
func mustParseInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(fmt.Sprintf("invalid int: %s", s))
}
return i
}
func TestMustParseInt_Panic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("expected panic")
} else if r != "invalid int: abc" {
t.Fatalf("unexpected panic message: %v", r)
}
}()
mustParseInt("abc")
}
- 仅对明确文档化“会 panic”的函数做此类测试;绝大多数 Go API 应返回
error,而非 panic - recover 必须在 defer 中注册,且必须在 panic 发生前设置好
- 不要在业务代码中用 recover 拦截未知 panic——这掩盖问题,违反 Go 的错误处理哲学
最容易被忽略的一点:错误处理的测试覆盖率常集中在“错误发生时”,却忘了验证“错误未发生时,主逻辑是否仍正确执行”。比如一个带重试的 HTTP 客户端,既要测第一次就失败,也要测重试三次后成功——而后者往往漏掉断言响应内容是否正确。










