# Golang 错误处理和测试 ## 问题 1. Go 的错误处理机制是什么?为什么不用 try-catch? 2. panic 和 recover 的使用场景是什么? 3. errors.Wrap 和 errors.Is 的作用是什么? 4. Go 的单元测试如何写?有哪些最佳实践? 5. table-driven test 是什么? 6. Go 的基准测试(Benchmark)如何写? --- ## 标准答案 ### 1. Go 的错误处理 #### **error 接口** ```go type error interface { Error() string } ``` **特点**: - 错误是值,不是异常 - 必须显式处理 - 避免嵌套过深 --- #### **创建错误** ```go import "errors" // 1. 简单错误 err1 := errors.New("something went wrong") // 2. fmt.Errorf err2 := fmt.Errorf("invalid user: %s", "alice") // 3. 错误包装(Go 1.13+) if err != nil { return fmt.Errorf("query failed: %w", err) } ``` --- #### **处理错误** ```go func readFile(path string) ([]byte, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read file failed: %w", err) } return data, nil } ``` --- ### 2. panic 和 recover #### **panic** **用途**:不可恢复的错误(程序无法继续运行) ```go // panic 示例 func doPanic() { panic("something terrible happened") } func main() { doPanic() fmt.Println("this will not print") } ``` --- #### **recover** **用途**:捕获 panic,防止程序崩溃 ```go func safeExecute() { defer func() { if err := recover(); err != nil { fmt.Println("recovered from:", err) } }() panic("oh no!") } func main() { safeExecute() fmt.Println("program continues") } ``` --- #### **最佳实践** 1. **避免使用 panic**:除非是真正不可恢复的错误 2. **recover 只在 defer 中使用**:其他地方无效 3. **库代码不应 panic**:应该返回 error --- ### 3. 错误包装和判断 #### **errors.Wrap** ```go import ( "errors" "fmt" ) func queryDB() error { return errors.New("connection failed") } func service() error { err := queryDB() if err != nil { return fmt.Errorf("service failed: %w", err) // 包装错误 } return nil } func main() { err := service() if err != nil { // 检查错误链 if errors.Is(err, errors.New("connection failed")) { fmt.Println("is connection error") } // 获取最底层的错误 var connErr error = errors.New("connection failed") if errors.Is(err, connErr) { fmt.Println("connection error occurred") } } } ``` --- #### **自定义错误类型** ```go import "errors" type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } func validate(name string) error { if name == "" { return &ValidationError{Field: "name", Message: "required"} } return nil } func main() { err := validate("") var ve *ValidationError if errors.As(err, &ve) { fmt.Println("validation error:", ve.Field, ve.Message) } } ``` --- ### 4. 单元测试 #### **基本测试** ```go package mypackage import "testing" func Add(a, b int) int { return a + b } func TestAdd(t *testing.T) { result := Add(1, 2) if result != 3 { t.Errorf("Add(1, 2) = %d; want 3", result) } } ``` --- #### **Table-Driven Test(表格驱动测试)** ```go func TestAddTableDriven(t *testing.T) { tests := []struct { a, b int want int }{ {1, 2, 3}, {2, 3, 5}, {10, -5, 5}, } for _, tt := range tests { t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) { if got := Add(tt.a, tt.b); got != tt.want { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) } }) } } ``` --- #### **子测试(Subtests)** ```go func TestAll(t *testing.T) { t.Run("Test1", func(t *testing.T) { // Test1 }) t.Run("Test2", func(t *testing.T) { // Test2 }) } ``` --- ### 5. 基准测试(Benchmark) #### **基本 Benchmark** ```go func BenchmarkAdd(b *testing.B) { for i := 0; i < b.N; i++ { Add(1, 2) } } ``` **运行**: ```bash go test -bench=. -benchmem ``` --- #### **Benchmark 高级用法** ```go func BenchmarkAddParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { Add(1, 2) } }) } func BenchmarkLargeSlice(b *testing.B) { b.ResetTimer() // 重置计时器 data := make([]int, 1000000) b.StartTimer() for i := 0; i < b.N; i++ { _ = len(data) } } ``` --- ### 6. Mock 和测试替身 #### **使用接口进行 Mock** ```go type Database interface { Query(id int) (string, error) } type Service struct { db Database } func (s *Service) GetUser(id int) (string, error) { return s.db.Query(id) } // Mock 实现 type MockDatabase struct { MockQuery func(id int) (string, error) } func (m *MockDatabase) Query(id int) (string, error) { return m.MockQuery(id) } // 测试 func TestService_GetUser(t *testing.T) { mockDB := &MockDatabase{ MockQuery: func(id int) (string, error) { return "Alice", nil }, } service := &Service{db: mockDB} name, err := service.GetUser(1) if err != nil { t.Errorf("unexpected error: %v", err) } if name != "Alice" { t.Errorf("got %s; want Alice", name) } } ``` --- ### 7. 测试最佳实践 #### **1. 测试覆盖率** ```bash # 查看覆盖率 go test -cover # 生成覆盖率报告 go test -coverprofile=coverage.out go tool cover -html=coverage.out ``` --- #### **2. 测试隔离** ```go // 每个测试独立 func Test1(t *testing.T) { // setup // test // teardown } func Test2(t *testing.T) { // 不依赖 Test1 } ``` --- #### **3. 使用 TestMain** ```go func TestMain(m *testing.M) { // 全局 setup fmt.Println("setup before all tests") m.Run() // 全局 teardown fmt.Println("teardown after all tests") } ``` --- ### 8. 阿里 P7 加分项 **深度理解**: - 理解 Go 错误处理的设计哲学(显式优于隐式) - 理解 panic/recover 的实现机制 - 理解 table-driven test 的优势 **实战经验**: - 有编写高质量单元测试的经验(覆盖率 > 80%) - 有使用 Mock 进行测试的经验 - 有编写 Benchmark 进行性能优化的经验 **测试策略**: - 理解如何设计可测试的代码(依赖注入、接口) - 理解如何进行集成测试和端到端测试 - 理解如何设计测试用例(边界条件、异常情况)