Golang错误处理的基本逻辑
Golang采用显式错误返回的设计,函数通常会将error作为最后一个返回值,调用方需要主动判断错误是否为nil来决定后续执行逻辑。常见的错误处理场景包括参数校验失败、外部依赖调用异常、资源操作失败等,这些场景对应的错误分支都需要被单元测试覆盖,才能确保程序在异常情况下也能按预期运行。

基础错误处理示例
下面是一个简单的用户查询函数,包含正常返回和错误返回两种分支:
package service
import (
"errors"
"fmt"
)
// User 用户信息结构体
type User struct {
ID int
Name string
}
// 模拟用户数据
var userDB = map[int]User{
1: {ID: 1, Name: "张三"},
2: {ID: 2, Name: "李四"},
}
// GetUserByID 根据ID查询用户,存在则返回用户,不存在返回错误
func GetUserByID(id int) (User, error) {
// 参数校验分支
if id <= 0 {
return User{}, errors.New("用户ID必须为正整数")
}
// 查询分支
user, exists := userDB[id]
if !exists {
return User{}, fmt.Errorf("ID为%d的用户不存在", id)
}
return user, nil
}
错误分支覆盖的核心技巧
1. 模拟错误返回
要测试错误分支,首先需要让函数返回预期的错误。对于依赖外部资源的函数,可以通过替换依赖为测试专用的模拟实现来触发错误。如果错误是函数内部直接生成的,只需要传入不符合要求的参数即可触发对应错误分支。
2. 验证错误内容
测试错误分支时,不仅要确认函数返回了错误,还要验证错误的类型或内容是否符合预期,避免错误被错误类型掩盖。Golang中可以使用errors.Is或者类型断言来判断错误是否符合预期。
3. 验证后续逻辑执行
有些错误处理逻辑会执行额外的操作,比如日志记录、资源回滚、告警触发等,测试时也需要验证这些操作是否被正确执行。
完整单元测试示例
针对上面的GetUserByID函数,我们可以编写如下单元测试,覆盖所有分支:
package service
import (
"errors"
"testing"
)
// TestGetUserByID 测试GetUserByID函数的所有分支
func TestGetUserByID(t *testing.T) {
tests := []struct {
name string
inputID int
wantUser User
wantErr error
}{
{
name: "参数校验失败-ID为非正整数",
inputID: 0,
wantUser: User{},
wantErr: errors.New("用户ID必须为正整数"),
},
{
name: "用户不存在",
inputID: 3,
wantUser: User{},
wantErr: errors.New("ID为3的用户不存在"),
},
{
name: "查询成功",
inputID: 1,
wantUser: User{ID: 1, Name: "张三"},
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotUser, err := GetUserByID(tt.inputID)
// 验证错误是否符合预期
if tt.wantErr != nil {
if err == nil {
t.Errorf("期望返回错误,实际未返回错误")
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("期望错误内容为%q,实际为%q", tt.wantErr.Error(), err.Error())
}
} else {
if err != nil {
t.Errorf("期望不返回错误,实际返回错误: %v", err)
return
}
}
// 验证返回的用户数据是否符合预期
if gotUser != tt.wantUser {
t.Errorf("期望用户数据为%v,实际为%v", tt.wantUser, gotUser)
}
})
}
}
常见注意事项
- 不要在测试中使用t.Fatal代替t.Error,t.Fatal会终止当前测试子函数,可能导致后续断言无法执行,优先使用t.Error记录错误。
- 对于返回自定义错误类型的场景,可以使用errors.As进行类型断言,验证错误的具体类型是否符合预期。
- 如果错误处理依赖外部服务,比如数据库、HTTP接口,建议在测试中使用mock工具或者内存模拟实现,避免测试依赖外部环境导致不稳定。
- 测试覆盖率工具可以帮助我们快速定位未被覆盖的错误分支,Golang自带的go test -cover命令可以查看测试覆盖率情况。
总结
错误处理是Golang程序的重要组成部分,对应的错误分支测试不能遗漏。通过传入边界参数触发错误、验证错误内容和后续执行逻辑,结合表格驱动测试的方式,可以高效覆盖所有错误分支。编写单元测试时养成覆盖错误分支的习惯,能够大幅降低程序上线后出现未预期异常的概率,提升整体代码质量。