Golang测试:你的方法与观点

Golang测试:你的方法与观点 关于Go语言测试的讨论在互联网上非常广泛。我已经阅读了大量相关内容。我不希望在这里进行任何比较。相反,我想了解您的观点、您的感受、您的推理

我必须承认,最初接触Go语言时,我立刻怀念起了assert。我写了一些类似assert的自定义函数,然后发现了testify,接着……接着我放弃了testify并删除了自己的函数。现在,我确实欣赏经典testing包的简洁性。它确实轻量级。确实不需要学习一门子语言。到目前为止,我甚至一次也没有怀念过任何东西——而且我已经写了几百个测试(我喜欢写测试——别问我为什么),许多基准测试(就是喜欢它们!)和示例(就是喜欢它们!)。

您如何看待Go语言中的测试?您是否在Go中使用更高级的测试框架,比如testify?还是您和我走同样的路,所以使用基本的测试方法,不是因为您没有时间学习testify(好吧,它不需要太多时间来学习,不是吗?),而是因为您同意Go语言创建者的观点,认为基本方法正是您需要和喜欢的?

也许在某些情况下,基本方法是不够的?如果是这样,我很乐意听听这些情况,至少当我自己遇到这些问题时能有所准备。


更多关于Golang测试:你的方法与观点的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

感谢分享——这是一篇有趣的论文!

也感谢你的观点。对我来说,很难想象大型项目,因为我是一名数据科学家,我的程序很少超过几千行,而且通常只有几百行。但我理解你在论文中提出的观点,并且可以想象在庞大的包中,主代码和测试代码的复杂性都会不断增加。

更多关于Golang测试:你的方法与观点的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这取决于你如何维护你的包。我发现外面的大多数指南只适用于小型和基础的包。

我写了一篇关于在Go中进行大规模单元测试的论文,不需要额外的框架,只需要养成习惯。就我个人而言,在测试中我使用BDD多于TDD,因为后者有持续且繁琐的维护成本。

我完全理解你对Go测试的探索历程。让我分享我的观点和实际经验。

我的测试哲学演变

和你一样,我也经历了类似的旅程。最初,我确实尝试过testify,但最终回归到了标准库的testing包。原因很简单:简洁性带来可维护性

// 这是我现在的典型测试写法
func TestUserValidation(t *testing.T) {
    tests := []struct {
        name     string
        user     User
        wantErr  bool
    }{
        {
            name:    "valid user",
            user:    User{Name: "John", Email: "john@example.com"},
            wantErr: false,
        },
        {
            name:    "empty name",
            user:    User{Name: "", Email: "john@example.com"},
            wantErr: true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := tt.user.Validate()
            if (err != nil) != tt.wantErr {
                t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}

为什么坚持标准库

  1. 零依赖:我的项目不引入外部测试依赖,构建保持纯净
  2. 一致性:团队中的每个人都理解相同的测试模式
  3. 性能:测试运行更快,没有框架开销
  4. 可调试性:失败信息直接明了,不需要理解框架的断言格式

表格驱动测试的力量

Go的标准测试方式真正闪耀的地方在于表格驱动测试:

func TestCalculateDiscount(t *testing.T) {
    tests := []struct {
        name          string
        price         float64
        customerType  string
        expected      float64
        shouldError   bool
    }{
        {"regular customer", 100.0, "regular", 100.0, false},
        {"vip customer", 100.0, "vip", 90.0, false},
        {"invalid type", 100.0, "unknown", 0.0, true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := CalculateDiscount(tt.price, tt.customerType)
            
            if tt.shouldError {
                if err == nil {
                    t.Error("expected error but got none")
                }
                return
            }
            
            if err != nil {
                t.Errorf("unexpected error: %v", err)
                return
            }
            
            if got != tt.expected {
                t.Errorf("got %v, want %v", got, tt.expected)
            }
        })
    }
}

何时需要更多

确实存在标准库不够用的情况:

  1. HTTP测试:这时我会使用net/http/httptest

    func TestAPIHandler(t *testing.T) {
        req := httptest.NewRequest("GET", "/api/users", nil)
        w := httptest.NewRecorder()
        
        handler(w, req)
        
        if w.Code != http.StatusOK {
            t.Errorf("expected status 200, got %d", w.Code)
        }
    }
    
  2. 并发测试:使用sync.WaitGroupgoroutine

    func TestConcurrentAccess(t *testing.T) {
        var counter int
        var mu sync.Mutex
        var wg sync.WaitGroup
        
        for i := 0; i < 1000; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                mu.Lock()
                counter++
                mu.Unlock()
            }()
        }
        
        wg.Wait()
        if counter != 1000 {
            t.Errorf("expected 1000, got %d", counter)
        }
    }
    
  3. 集成测试:需要外部资源时,我会创建testhelpers

    // testhelpers/database.go
    func NewTestDB(t *testing.T) *sql.DB {
        db, err := sql.Open("postgres", "test_connection_string")
        if err != nil {
            t.Fatalf("failed to connect to test db: %v", err)
        }
        t.Cleanup(func() { db.Close() })
        return db
    }
    

基准测试和示例

我也大量使用基准测试和示例,它们与标准测试包完美集成:

func BenchmarkStringConcatenation(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var result string
        for j := 0; j < 100; j++ {
            result += "a"
        }
        _ = result
    }
}

func ExampleGreet() {
    fmt.Println(Greet("World"))
    // Output: Hello, World
}

结论

我选择标准库测试方法不是因为它总是最简单,而是因为它提供了最佳的长期维护性。当遇到复杂场景时,我会编写辅助函数而不是引入框架:

// 自定义的测试辅助函数
func assertEqual(t *testing.T, got, want interface{}) {
    t.Helper()
    if !reflect.DeepEqual(got, want) {
        t.Errorf("got %v, want %v", got, want)
    }
}

func assertNoError(t *testing.T, err error) {
    t.Helper()
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
}

这种方式让我保持对测试的完全控制,理解每一个失败,并且确保测试代码和产品代码一样清晰可维护。

回到顶部