Go 项目编写高质量单元测试

225 阅读3分钟

以下是针对Golang项目编写高质量单元测试的完整指南,结合testing包的常用使用方式和最佳实践:


一、单元测试基础结构

  1. ​文件命名规范​

    • 测试文件必须位于与被测代码相同的包中,并以_test.go结尾(如user_service_test.go
  2. ​测试函数格式​

    func TestXxx(t *testing.T) {
        // 测试逻辑
    }
    
    • 函数名以Test开头,后接首字母大写的被测函数名(如TestAdd
    • 参数必须为*testing.T类型,用于管理测试状态和错误报告
  3. ​运行测试命令​

    go test -v ./...      # 运行所有测试并显示详细信息
    go test -run TestAdd  # 仅运行指定测试函数
    

二、测试组织方式

  1. ​表驱动测试(Table-Driven Tests)​
    通过结构体切片组织多组测试用例,提升可维护性:

    func TestAdd(t *testing.T) {
        tests := []struct {
            name   string
            a, b   int
            want   int
        }{
            {"正数相加", 2, 3, 5},
            {"负数相加", -1, -1, -2},
            {"零值处理", 0, 0, 0},
        }
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                got := Add(tt.a, tt.b)
                if got != tt.want {
                    t.Errorf("期望 %d, 实际 %d", tt.want, got)
                }
            })
        }
    }
    
  2. ​子测试与并行执行​

    t.Run("子测试名称", func(t *testing.T) {
        t.Parallel()  // 标记为可并行执行
        // 测试逻辑
    })
    

    使用-parallel N参数控制并行度(如go test -parallel 4


三、断言与错误处理

  1. ​原生错误报告方法​

    t.Error("描述")     // 标记失败但继续执行
    t.Errorf("格式化错误") 
    t.Fatal("致命错误")  // 立即终止当前测试
    
  2. ​第三方断言库(推荐testify)​

    import "github.com/stretchr/testify/assert"
    
    func TestDivision(t *testing.T) {
        result, err := Division(10, 2)
        assert.Nil(t, err)
        assert.Equal(t, 5.0, result)
        assert.ErrorContains(t, err, "除数不能为0")
    }
    

    提供更丰富的断言方法和详细错误信息


四、依赖模拟(Mock)

  1. ​接口模拟(使用gomock)​

    go install go.uber.org/mock/mockgen@latest
    mockgen -source=user_repo.go -destination=mocks/user_repo_mock.go
    

    测试用例示例:

    func TestGetUser(t *testing.T) {
        ctrl := gomock.NewController(t)
        defer ctrl.Finish()
        
        mockRepo := mocks.NewMockUserRepository(ctrl)
        mockRepo.EXPECT().GetUser(1).Return(&User{Name: "Alice"}, nil)
        
        service := NewUserService(mockRepo)
        user, err := service.GetUser(1)
        assert.Equal(t, "Alice", user.Name)
    }
    
  2. ​HTTP处理器测试​

    func TestIndexHandler(t *testing.T) {
        req := httptest.NewRequest("GET", "/", nil)
        rr := httptest.NewRecorder()
        
        IndexHandler(rr, req)
        assert.Equal(t, http.StatusOK, rr.Code)
        assert.Contains(t, rr.Body.String(), "Hello World")
    }
    

五、覆盖率分析

go test -coverprofile=coverage.out  # 生成覆盖率文件
go tool cover -html=coverage.out    # 生成可视化报告
go test -covermode=atomic -coverpkg=./...  # 全包覆盖率分析
  • 推荐保持核心逻辑覆盖率80%+

六、常用测试参数

参数作用
-v显示详细测试日志
-count=N重复执行测试N次(用于排查偶现问题)
-timeout=30s设置超时时间
-bench=.执行基准测试
-cpu=1,2,4测试不同CPU核心数的表现

七、编写可测试代码原则

  1. ​依赖注入设计​

    type UserService struct {
        repo UserRepository
    }
    
    func NewUserService(repo UserRepository) *UserService {
        return &UserService{repo: repo}
    }
    
  2. ​分离业务逻辑与I/O操作​
    将核心算法与数据库/网络调用解耦,便于单独测试

  3. ​避免全局状态​
    使用结构体封装状态而非全局变量,防止测试间污染


以上方法结合了标准库testing包的核心功能(基础测试、子测试、覆盖率)和业界最佳实践(表驱动测试、依赖注入、Mock技术),可帮助构建健壮且可维护的测试体系。建议根据项目复杂度选择是否引入testifygomock等第三方库来增强测试能力。