move claude-marketplace to ai-proj-helper

This commit is contained in:
2026-03-12 21:42:30 +08:00
parent d7b6835e1d
commit 43585b8504
188 changed files with 39510 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
# Go 后端测试
## 测试框架
- **testify**: 断言和套件
- **httptest**: HTTP 测试
- **gomock**: Mock 生成(仅用于 handler 层)
## ⚠️ Biz 层测试规则:禁止使用 Mock
**Biz/Service 层测试必须使用真实 PostgreSQL test DB不允许使用 mock store。**
Mock store 只是在测试你的 mock 实现,无法验证真实的 SQL 行为、事务、FK 约束等。
| 层 | 测试方式 | 原因 |
|----|---------|------|
| model/store | **test DB** (PostgreSQL) | 验证真实 SQL/ORM 行为 |
| biz/service | **test DB** (PostgreSQL) + 真实 store | 验证业务逻辑 + 真实数据交互 |
| handler | **mock biz + httptest** | 只测 HTTP 路由和参数绑定 |
```go
// ✅ 正确 — biz 层使用真实 test DB + 真实 store
func setupBiz(t *testing.T) (*SomeBiz, *gorm.DB) {
db := newTestDB(t)
s := store.NewSomeStore(db)
biz := NewSomeBiz(s)
t.Cleanup(func() {
db.Exec("DELETE FROM some_table WHERE tenant_id = ?", testTenantID)
})
return biz, db
}
// ❌ 错误 — biz 层使用 mock store等于没测
mockStore := store.NewMockIStore(ctrl)
mockStore.EXPECT().Get(gomock.Any(), id).Return(fakeData, nil)
biz := NewSomeBiz(mockStore)
```
### testdb_test.go 模板
```go
package biz
import (
"os"
"testing"
"github.com/stretchr/testify/require"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
const testTenantID int64 = 99
func newTestDB(t *testing.T, models ...interface{}) *gorm.DB {
t.Helper()
dsn := os.Getenv("TEST_DATABASE_DSN")
if dsn == "" {
dsn = "host=localhost user=coolbuy-dev dbname=coolbuy_paas_test sslmode=disable"
}
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(models...))
return db
}
```
## 运行测试
```bash
# 所有测试
go test ./...
make test
# 带覆盖率
make cover
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# 特定包
go test -v ./internal/twms/biz/...
# 特定函数
go test -v -run TestFunctionName ./...
```
## Biz 层单元测试模板(真实 DB
```go
package biz
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"project/internal/user/model"
"project/internal/user/store"
)
func setupUserBiz(t *testing.T) (*UserBiz, *gorm.DB) {
db := newTestDB(t, &model.User{})
s := store.NewUserStore(db)
biz := NewUserBiz(s)
t.Cleanup(func() {
db.Exec("DELETE FROM users WHERE tenant_id = ?", testTenantID)
})
return biz, db
}
func createTestUser(t *testing.T, db *gorm.DB, username string) *model.User {
t.Helper()
user := &model.User{TenantID: testTenantID, Username: username, Email: username + "@test.com"}
require.NoError(t, db.Create(user).Error)
return user
}
func TestUserBiz_Get(t *testing.T) {
biz, db := setupUserBiz(t)
user := createTestUser(t, db, "john")
result, err := biz.Get(context.Background(), user.ID)
assert.NoError(t, err)
assert.Equal(t, "john", result.Username)
}
func TestUserBiz_Get_NotFound(t *testing.T) {
biz, _ := setupUserBiz(t)
_, err := biz.Get(context.Background(), 99999)
assert.Error(t, err)
}
```
## 表驱动测试
```go
func TestValidateUsername(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{"valid", "john_doe", false},
{"too_short", "ab", true},
{"too_long", strings.Repeat("a", 65), true},
{"special_chars", "user@name", true},
{"empty", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateUsername(tt.input)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
```
## HTTP Handler 测试
```go
func TestUserController_List(t *testing.T) {
gin.SetMode(gin.TestMode)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBiz := biz.NewMockIBiz(ctrl)
mockUserBiz := biz.NewMockUserBiz(ctrl)
mockBiz.EXPECT().Users().Return(mockUserBiz).AnyTimes()
mockUserBiz.EXPECT().List(gomock.Any(), gomock.Any()).Return(&v1.ListUsersResponse{
Total: 1,
Users: []*v1.User{{Id: 1, Username: "test"}},
}, nil)
controller := NewUserController(mockBiz)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest("GET", "/v1/users?page=1&limit=10", nil)
controller.List(c)
assert.Equal(t, http.StatusOK, w.Code)
}
```
## Mock 生成
```bash
# 生成 Mock
mockgen -source=internal/twms/store/store.go \
-destination=internal/twms/store/mock_store.go \
-package=store
# go:generate 方式
//go:generate mockgen -source=store.go -destination=mock_store.go -package=store
```