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