API测试 #
一、API测试概述 #
1.1 测试层级 #
text
单元测试 → 集成测试 → API测试 → 端到端测试
1.2 API测试内容 #
| 内容 | 说明 |
|---|---|
| 请求验证 | 参数、格式验证 |
| 响应验证 | 状态码、数据格式 |
| 业务逻辑 | 业务规则验证 |
| 错误处理 | 错误响应验证 |
二、集成测试 #
2.1 测试数据库 #
go
func setupTestDB() *gorm.DB {
dsn := "file::memory:?cache=shared"
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 自动迁移
db.AutoMigrate(&User{}, &Post{})
return db
}
func TestUserAPI(t *testing.T) {
db := setupTestDB()
r := gin.New()
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
var user User
if err := db.First(&user, id).Error; err != nil {
c.JSON(404, gin.H{"error": "not found"})
return
}
c.JSON(200, user)
})
// 创建测试数据
db.Create(&User{Name: "Alice"})
// 测试
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/1", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}
2.2 测试完整API #
go
func TestUserAPI_CRUD(t *testing.T) {
db := setupTestDB()
r := setupRouter(db)
// 测试创建
t.Run("Create", func(t *testing.T) {
body := `{"name": "Alice", "email": "alice@example.com"}`
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, 201, w.Code)
})
// 测试获取
t.Run("Get", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/1", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
})
// 测试更新
t.Run("Update", func(t *testing.T) {
body := `{"name": "Alice Updated"}`
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/users/1", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
})
// 测试删除
t.Run("Delete", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/users/1", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 204, w.Code)
})
}
三、测试工具 #
3.1 testify #
bash
go get github.com/stretchr/testify
go
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type UserTestSuite struct {
suite.Suite
db *gorm.DB
r *gin.Engine
}
func (s *UserTestSuite) SetupSuite() {
s.db = setupTestDB()
s.r = setupRouter(s.db)
}
func (s *UserTestSuite) TearDownSuite() {
sqlDB, _ := s.db.DB()
sqlDB.Close()
}
func (s *UserTestSuite) TestCreateUser() {
body := `{"name": "Alice"}`
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
s.r.ServeHTTP(w, req)
s.Equal(201, w.Code)
}
func TestUserTestSuite(t *testing.T) {
suite.Run(t, new(UserTestSuite))
}
3.2 ginkgo #
bash
go get github.com/onsi/ginkgo/v2
go get github.com/onsi/gomega
go
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("User API", func() {
var (
db *gorm.DB
r *gin.Engine
)
BeforeEach(func() {
db = setupTestDB()
r = setupRouter(db)
})
AfterEach(func() {
sqlDB, _ := db.DB()
sqlDB.Close()
})
Describe("Create User", func() {
It("should create a user", func() {
body := `{"name": "Alice"}`
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
Expect(w.Code).To(Equal(201))
})
})
})
四、测试场景 #
4.1 测试认证 #
go
func TestProtectedAPI(t *testing.T) {
r := setupRouter()
// 获取Token
body := `{"username": "admin", "password": "admin"}`
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/login", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
var response map[string]string
json.Unmarshal(w.Body.Bytes(), &response)
token := response["token"]
// 使用Token访问
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", "Bearer "+token)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}
4.2 测试分页 #
go
func TestPagination(t *testing.T) {
db := setupTestDB()
// 创建测试数据
for i := 0; i < 25; i++ {
db.Create(&User{Name: fmt.Sprintf("User%d", i)})
}
r := setupRouter(db)
// 测试第一页
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users?page=1&page_size=10", nil)
r.ServeHTTP(w, req)
var response map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &response)
assert.Equal(t, 200, w.Code)
assert.Equal(t, float64(10), response["data"].([]interface{})[0])
}
4.3 测试错误处理 #
go
func TestErrorHandling(t *testing.T) {
r := setupRouter()
tests := []struct {
name string
path string
expectCode int
}{
{"not found", "/users/999", 404},
{"invalid id", "/users/abc", 400},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", tt.path, nil)
r.ServeHTTP(w, req)
assert.Equal(t, tt.expectCode, w.Code)
})
}
}
五、性能测试 #
5.1 基准测试 #
go
func BenchmarkGetUser(b *testing.B) {
r := setupRouter()
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/1", nil)
r.ServeHTTP(w, req)
}
}
5.2 并发测试 #
go
func TestConcurrentRequests(t *testing.T) {
r := setupRouter()
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}()
}
wg.Wait()
}
六、总结 #
6.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 集成测试 | 测试组件协作 |
| 测试工具 | testify、ginkgo |
| 测试场景 | 认证、分页、错误 |
| 性能测试 | 基准测试、并发测试 |
6.2 最佳实践 #
| 实践 | 说明 |
|---|---|
| 测试套件 | 使用测试套件组织测试 |
| 清理数据 | 每个测试后清理数据 |
| 独立测试 | 测试之间不依赖 |
| 覆盖场景 | 测试正常和异常场景 |
6.3 下一步 #
现在你已经掌握了API测试,接下来让我们学习 Docker部署,了解容器化部署!
最后更新:2026-03-28