测试框架 Testify
Testify 是 Go 语言最流行的测试工具包,提供了丰富的断言和 Mock 功能。
📋 学习目标
- 理解测试的重要性
- 掌握 Testify 的基本使用
- 学会使用断言
- 理解 Mock 的使用
- 掌握测试套件
- 了解测试最佳实践
🎯 Testify 简介
为什么使用 Testify
- 丰富的断言: 提供多种断言方法
- Mock 支持: 支持接口 Mock
- 测试套件: 支持测试组织和共享
- 易于使用: API 简洁直观
- 社区活跃: 使用广泛
安装 Testify
bash
go get github.com/stretchr/testify🚀 快速开始
基本测试
go
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAdd(t *testing.T) {
result := Add(2, 3)
assert.Equal(t, 5, result)
}
func Add(a, b int) int {
return a + b
}✅ 断言
基本断言
go
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAssertions(t *testing.T) {
// 相等断言
assert.Equal(t, 5, 5)
assert.NotEqual(t, 5, 6)
// 布尔断言
assert.True(t, true)
assert.False(t, false)
// Nil 断言
var ptr *int
assert.Nil(t, ptr)
assert.NotNil(t, &ptr)
// 错误断言
err := someFunction()
assert.NoError(t, err)
assert.Error(t, err)
// 包含断言
assert.Contains(t, "Hello World", "World")
assert.NotContains(t, "Hello", "World")
// 长度断言
assert.Len(t, []int{1, 2, 3}, 3)
// 空断言
assert.Empty(t, "")
assert.NotEmpty(t, "hello")
}使用 require
go
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestRequire(t *testing.T) {
// require 在失败时会立即停止测试
require.Equal(t, 5, 5)
require.NotNil(t, someObject)
}🎭 Mock
创建 Mock
go
package main
import (
"testing"
"github.com/stretchr/testify/mock"
)
// 定义接口
type UserRepository interface {
GetUser(id int) (*User, error)
CreateUser(user *User) error
}
// Mock 实现
type MockUserRepository struct {
mock.Mock
}
func (m *MockUserRepository) GetUser(id int) (*User, error) {
args := m.Called(id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*User), args.Error(1)
}
func (m *MockUserRepository) CreateUser(user *User) error {
args := m.Called(user)
return args.Error(0)
}
// 使用 Mock
func TestGetUser(t *testing.T) {
mockRepo := new(MockUserRepository)
// 设置期望
expectedUser := &User{ID: 1, Name: "张三"}
mockRepo.On("GetUser", 1).Return(expectedUser, nil)
// 执行测试
user, err := mockRepo.GetUser(1)
// 验证
assert.NoError(t, err)
assert.Equal(t, expectedUser, user)
mockRepo.AssertExpectations(t)
}📦 测试套件
使用 Suite
go
package main
import (
"testing"
"github.com/stretchr/testify/suite"
)
type UserServiceTestSuite struct {
suite.Suite
service *UserService
}
// SetupTest 在每个测试前执行
func (suite *UserServiceTestSuite) SetupTest() {
suite.service = NewUserService()
}
// TearDownTest 在每个测试后执行
func (suite *UserServiceTestSuite) TearDownTest() {
// 清理工作
}
// SetupSuite 在套件开始前执行一次
func (suite *UserServiceTestSuite) SetupSuite() {
// 初始化工作
}
// TearDownSuite 在套件结束后执行一次
func (suite *UserServiceTestSuite) TearDownSuite() {
// 清理工作
}
// 测试方法
func (suite *UserServiceTestSuite) TestCreateUser() {
user := &User{Name: "张三", Email: "zhangsan@example.com"}
err := suite.service.CreateUser(user)
suite.NoError(err)
suite.NotNil(user.ID)
}
func (suite *UserServiceTestSuite) TestGetUser() {
user, err := suite.service.GetUser(1)
suite.NoError(err)
suite.NotNil(user)
}
// 运行测试套件
func TestUserServiceTestSuite(t *testing.T) {
suite.Run(t, new(UserServiceTestSuite))
}🏃♂️ 实践应用
HTTP 测试
go
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestGetUser(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/users/:id", GetUser)
req, _ := http.NewRequest("GET", "/users/1", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "user_id")
}数据库测试
go
package main
import (
"testing"
"github.com/stretchr/testify/assert"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func TestUserRepository(t *testing.T) {
// 使用内存数据库
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
assert.NoError(t, err)
// 自动迁移
db.AutoMigrate(&User{})
repo := NewUserRepository(db)
// 创建用户
user := &User{Name: "张三", Email: "zhangsan@example.com"}
err = repo.Create(user)
assert.NoError(t, err)
assert.NotZero(t, user.ID)
// 查询用户
found, err := repo.GetByID(user.ID)
assert.NoError(t, err)
assert.Equal(t, user.Name, found.Name)
}⚠️ 最佳实践
1. 测试命名
go
// ✅ 清晰的测试名称
func TestUserService_CreateUser_Success(t *testing.T) {}
func TestUserService_CreateUser_InvalidEmail(t *testing.T) {}2. 测试组织
go
// ✅ 使用表驱动测试
func TestAdd(t *testing.T) {
tests := []struct {
name string
a int
b int
expected int
}{
{"positive", 2, 3, 5},
{"negative", -2, -3, -5},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
assert.Equal(t, tt.expected, result)
})
}
}3. Mock 使用
go
// ✅ 验证 Mock 调用
mockRepo.AssertExpectations(t)
// ✅ 验证调用次数
mockRepo.AssertNumberOfCalls(t, "GetUser", 1)📚 扩展阅读
⏭️ 下一章节
任务调度 → 学习定时任务
💡 提示: 良好的测试是代码质量的保证,Testify 让编写测试变得更加简单高效!
