测试最佳实践

良好的测试习惯能让测试更有效,维护成本更低。

测试命名规范

func Test<函数名>_<场景>_<期望结果>(t *testing.T) {}

func TestGetUser_ValidID_ReturnsUser(t *testing.T) {}
func TestGetUser_InvalidID_ReturnsError(t *testing.T) {}
func TestCreateUser_MissingName_ReturnsBadRequest(t *testing.T) {}

测试组织

func TestUserAPI(t *testing.T) {
    gin.SetMode(gin.TestMode)
    
    t.Run("创建用户", func(t *testing.T) {
        t.Run("正常创建", func(t *testing.T) {
            // 测试代码
        })
        
        t.Run("参数验证失败", func(t *testing.T) {
            // 测试代码
        })
    })
    
    t.Run("获取用户", func(t *testing.T) {
        t.Run("用户存在", func(t *testing.T) {
            // 测试代码
        })
        
        t.Run("用户不存在", func(t *testing.T) {
            // 测试代码
        })
    })
}

测试辅助函数

type TestSuite struct {
    Router  *gin.Engine
    DB      *sql.DB
    Cleanup func()
}

func SetupTestSuite(t *testing.T) *TestSuite {
    gin.SetMode(gin.TestMode)
    
    db := setupTestDB(t)
    router := setupRouter(db)
    
    return &TestSuite{
        Router: router,
        DB:     db,
        Cleanup: func() {
            db.Close()
        },
    }
}

func TestWithSuite(t *testing.T) {
    suite := SetupTestSuite(t)
    defer suite.Cleanup()
    
    // 使用 suite.Router 和 suite.DB
}

请求构造器

type RequestBuilder struct {
    method  string
    path    string
    body    io.Reader
    headers map[string]string
    cookies []*http.Cookie
}

func NewRequest(method, path string) *RequestBuilder {
    return &RequestBuilder{
        method:  method,
        path:    path,
        headers: make(map[string]string),
    }
}

func (b *RequestBuilder) WithBody(body string) *RequestBuilder {
    b.body = strings.NewReader(body)
    return b
}

func (b *RequestBuilder) WithJSON(body interface{}) *RequestBuilder {
    data, _ := json.Marshal(body)
    b.body = bytes.NewReader(data)
    b.headers["Content-Type"] = "application/json"
    return b
}

func (b *RequestBuilder) WithHeader(key, value string) *RequestBuilder {
    b.headers[key] = value
    return b
}

func (b *RequestBuilder) WithAuth(token string) *RequestBuilder {
    b.headers["Authorization"] = "Bearer " + token
    return b
}

func (b *RequestBuilder) Build() *http.Request {
    req, _ := http.NewRequest(b.method, b.path, b.body)
    for k, v := range b.headers {
        req.Header.Set(k, v)
    }
    for _, c := range b.cookies {
        req.AddCookie(c)
    }
    return req
}

func TestWithBuilder(t *testing.T) {
    r := setupRouter()
    
    req := NewRequest("POST", "/users").
        WithJSON(map[string]string{"name": "张三"}).
        WithAuth("token").
        Build()
    
    w := httptest.NewRecorder()
    r.ServeHTTP(w, req)
    
    assert.Equal(t, http.StatusCreated, w.Code)
}

响应断言辅助

func AssertJSONEquals(t *testing.T, expected, actual string) {
    var expectedJSON, actualJSON interface{}
    json.Unmarshal([]byte(expected), &expectedJSON)
    json.Unmarshal([]byte(actual), &actualJSON)
    assert.Equal(t, expectedJSON, actualJSON)
}

func AssertStatusCode(t *testing.T, expected int, recorder *httptest.ResponseRecorder) {
    if recorder.Code != expected {
        t.Errorf("Expected status %d, got %d. Body: %s", 
            expected, recorder.Code, recorder.Body.String())
    }
}

func AssertError(t *testing.T, body string, expectedError string) {
    var response map[string]interface{}
    json.Unmarshal([]byte(body), &response)
    assert.Contains(t, response["error"], expectedError)
}

测试数据工厂

type UserFactory struct{}

func (f *UserFactory) Create(overrides ...func(*User)) *User {
    user := &User{
        ID:    1,
        Name:  "测试用户",
        Email: "test@example.com",
        Role:  "user",
    }
    
    for _, override := range overrides {
        override(user)
    }
    
    return user
}

func TestUserFactory(t *testing.T) {
    factory := &UserFactory{}
    
    admin := factory.Create(func(u *User) {
        u.Role = "admin"
    })
    
    assert.Equal(t, "admin", admin.Role)
}

Mock 接口

type UserRepository interface {
    FindByID(id int) (*User, error)
    Create(user *User) error
    Update(user *User) error
    Delete(id int) error
}

type MockUserRepository struct {
    FindByIDFunc func(id int) (*User, error)
    CreateFunc   func(user *User) error
    UpdateFunc   func(user *User) error
    DeleteFunc   func(id int) error
}

func (m *MockUserRepository) FindByID(id int) (*User, error) {
    return m.FindByIDFunc(id)
}

func (m *MockUserRepository) Create(user *User) error {
    return m.CreateFunc(user)
}

func TestWithMock(t *testing.T) {
    mock := &MockUserRepository{
        FindByIDFunc: func(id int) (*User, error) {
            return &User{ID: id, Name: "测试"}, nil
        },
    }
    
    user, err := mock.FindByID(1)
    assert.NoError(t, err)
    assert.Equal(t, 1, user.ID)
}

测试清理

func TestWithCleanup(t *testing.T) {
    // 创建测试数据
    user := createTestUser(t)
    
    // 注册清理函数
    t.Cleanup(func() {
        deleteTestUser(t, user.ID)
    })
    
    // 测试代码
}

func createTestUser(t *testing.T) *User {
    // 创建用户
    return &User{ID: 1}
}

func deleteTestUser(t *testing.T, id int) {
    // 删除用户
}

小结

良好的测试实践包括:清晰的命名、合理的组织、可复用的辅助函数、独立的数据工厂。测试应该快速、可靠、独立。使用 t.Cleanup 确保资源清理,使用 Mock 隔离外部依赖。测试代码和业务代码一样重要,值得用心维护。