zco-claude 0.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ClaudeSettings/DOT.claudeignore +7 -0
- ClaudeSettings/README.md +100 -0
- ClaudeSettings/commands/generate_changelog.sh +49 -0
- ClaudeSettings/commands/show_env +92 -0
- ClaudeSettings/commands/zco-clean +164 -0
- ClaudeSettings/commands/zco-git-summary +15 -0
- ClaudeSettings/commands/zco-git-tag +42 -0
- ClaudeSettings/hooks/CHANGELOG.md +157 -0
- ClaudeSettings/hooks/README.md +254 -0
- ClaudeSettings/hooks/save_chat_plain.py +148 -0
- ClaudeSettings/hooks/save_chat_spec.py +398 -0
- ClaudeSettings/rules/README.md +270 -0
- ClaudeSettings/rules/go/.golangci.yml.template +170 -0
- ClaudeSettings/rules/go/GoBuildAutoVersion.v250425.md +95 -0
- ClaudeSettings/rules/go/check-standards.sh +128 -0
- ClaudeSettings/rules/go/coding-standards.md +973 -0
- ClaudeSettings/rules/go/example.go +207 -0
- ClaudeSettings/rules/go/go-testing.md +691 -0
- ClaudeSettings/rules/go/list-comments.sh +85 -0
- ClaudeSettings/settings.sample.json +71 -0
- ClaudeSettings/skills/README.md +225 -0
- ClaudeSettings/skills/zco-docs-update/SKILL.md +381 -0
- ClaudeSettings/skills/zco-help/SKILL.md +601 -0
- ClaudeSettings/skills/zco-plan/SKILL.md +661 -0
- ClaudeSettings/skills/zco-plan-new/SKILL.md +585 -0
- ClaudeSettings/zco-scripts/co-docs-update.sh +150 -0
- ClaudeSettings/zco-scripts/test_update_plan_metadata.py +328 -0
- ClaudeSettings/zco-scripts/update-plan-metadata.py +324 -0
- zco_claude-0.0.8.dist-info/METADATA +190 -0
- zco_claude-0.0.8.dist-info/RECORD +34 -0
- zco_claude-0.0.8.dist-info/WHEEL +5 -0
- zco_claude-0.0.8.dist-info/entry_points.txt +3 -0
- zco_claude-0.0.8.dist-info/top_level.txt +1 -0
- zco_claude_init.py +1732 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
# Go 语言测试规则
|
|
2
|
+
|
|
3
|
+
## Go 测试基础规范
|
|
4
|
+
|
|
5
|
+
### 文件命名
|
|
6
|
+
|
|
7
|
+
**规则:测试文件必须以 `_test.go` 结尾。**
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
user.go → user_test.go
|
|
11
|
+
validator.go → validator_test.go
|
|
12
|
+
service.go → service_test.go
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 测试函数签名
|
|
16
|
+
|
|
17
|
+
**规则:测试函数必须符合 Go 测试规范。**
|
|
18
|
+
|
|
19
|
+
```go
|
|
20
|
+
// ✓ 正确
|
|
21
|
+
func TestRegisterUser(t *testing.T) {
|
|
22
|
+
// ...
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func TestRegisterUser_ValidInput(t *testing.T) {
|
|
26
|
+
// ...
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ❌ 错误
|
|
30
|
+
func testRegisterUser(t *testing.T) { // 小写开头
|
|
31
|
+
// ...
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func TestRegisterUser() { // 缺少 *testing.T 参数
|
|
35
|
+
// ...
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 包声明
|
|
40
|
+
|
|
41
|
+
**规则:测试文件可以使用相同包名或添加 `_test` 后缀。**
|
|
42
|
+
|
|
43
|
+
```go
|
|
44
|
+
// 方式 1:黑盒测试(推荐用于测试公共 API)
|
|
45
|
+
package user_test
|
|
46
|
+
|
|
47
|
+
import (
|
|
48
|
+
"testing"
|
|
49
|
+
"myapp/internal/user"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
func TestRegisterUser(t *testing.T) {
|
|
53
|
+
u, err := user.Register("test@example.com", "Pass123!")
|
|
54
|
+
// 只能访问公共 API
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 方式 2:白盒测试(用于测试内部逻辑)
|
|
58
|
+
package user
|
|
59
|
+
|
|
60
|
+
import "testing"
|
|
61
|
+
|
|
62
|
+
func TestValidateEmail(t *testing.T) {
|
|
63
|
+
err := validateEmail("test@example.com")
|
|
64
|
+
// 可以访问私有函数
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**选择建议:**
|
|
69
|
+
- 优先使用黑盒测试(`package xxx_test`)
|
|
70
|
+
- 只在必须测试私有函数时使用白盒测试
|
|
71
|
+
- 一个包可以同时有两种测试文件
|
|
72
|
+
|
|
73
|
+
## Go 测试工具
|
|
74
|
+
|
|
75
|
+
### 使用 testing 包
|
|
76
|
+
|
|
77
|
+
```go
|
|
78
|
+
import "testing"
|
|
79
|
+
|
|
80
|
+
func TestExample(t *testing.T) {
|
|
81
|
+
// t.Error/t.Errorf - 报告错误但继续执行
|
|
82
|
+
if got != want {
|
|
83
|
+
t.Errorf("got %v, want %v", got, want)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// t.Fatal/t.Fatalf - 报告错误并停止测试
|
|
87
|
+
if err != nil {
|
|
88
|
+
t.Fatalf("unexpected error: %v", err)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// t.Log/t.Logf - 记录信息(只在失败或 -v 时显示)
|
|
92
|
+
t.Logf("Processing user: %s", user.Email)
|
|
93
|
+
|
|
94
|
+
// t.Skip/t.Skipf - 跳过测试
|
|
95
|
+
if runtime.GOOS == "windows" {
|
|
96
|
+
t.Skip("skipping test on Windows")
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 推荐使用 testify/assert
|
|
102
|
+
|
|
103
|
+
**安装:**
|
|
104
|
+
```bash
|
|
105
|
+
go get github.com/stretchr/testify/assert
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**使用:**
|
|
109
|
+
```go
|
|
110
|
+
import (
|
|
111
|
+
"testing"
|
|
112
|
+
"github.com/stretchr/testify/assert"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
func TestRegisterUser(t *testing.T) {
|
|
116
|
+
user, err := RegisterUser("test@example.com", "Pass123!")
|
|
117
|
+
|
|
118
|
+
// 更清晰的断言
|
|
119
|
+
assert.NoError(t, err)
|
|
120
|
+
assert.NotNil(t, user)
|
|
121
|
+
assert.Equal(t, "test@example.com", user.Email)
|
|
122
|
+
assert.NotEmpty(t, user.ID)
|
|
123
|
+
assert.True(t, len(user.ID) > 0)
|
|
124
|
+
assert.Contains(t, user.Email, "@")
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 推荐使用 testify/mock(如果需要 mock)
|
|
129
|
+
|
|
130
|
+
```go
|
|
131
|
+
import (
|
|
132
|
+
"github.com/stretchr/testify/mock"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
type MockUserRepository struct {
|
|
136
|
+
mock.Mock
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func (m *MockUserRepository) Save(user *User) error {
|
|
140
|
+
args := m.Called(user)
|
|
141
|
+
return args.Error(0)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
func (m *MockUserRepository) FindByEmail(email string) (*User, error) {
|
|
145
|
+
args := m.Called(email)
|
|
146
|
+
if args.Get(0) == nil {
|
|
147
|
+
return nil, args.Error(1)
|
|
148
|
+
}
|
|
149
|
+
return args.Get(0).(*User), args.Error(1)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 在测试中使用
|
|
153
|
+
func TestRegisterUser_EmailExists_ReturnsError(t *testing.T) {
|
|
154
|
+
mockRepo := new(MockUserRepository)
|
|
155
|
+
mockRepo.On("FindByEmail", "existing@example.com").
|
|
156
|
+
Return(&User{Email: "existing@example.com"}, nil)
|
|
157
|
+
|
|
158
|
+
service := NewUserService(mockRepo)
|
|
159
|
+
_, err := service.RegisterUser("existing@example.com", "Pass123!")
|
|
160
|
+
|
|
161
|
+
assert.Error(t, err)
|
|
162
|
+
mockRepo.AssertExpectations(t)
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 表驱动测试(Table-Driven Tests)
|
|
167
|
+
|
|
168
|
+
**Go 推荐的测试模式,适合测试多个相似场景。**
|
|
169
|
+
|
|
170
|
+
### 基本结构
|
|
171
|
+
|
|
172
|
+
```go
|
|
173
|
+
func TestValidateEmail(t *testing.T) {
|
|
174
|
+
tests := []struct {
|
|
175
|
+
name string // 测试名称
|
|
176
|
+
email string // 输入
|
|
177
|
+
wantErr bool // 期望是否有错误
|
|
178
|
+
}{
|
|
179
|
+
{
|
|
180
|
+
name: "有效邮箱",
|
|
181
|
+
email: "user@example.com",
|
|
182
|
+
wantErr: false,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "缺少@符号",
|
|
186
|
+
email: "userexample.com",
|
|
187
|
+
wantErr: true,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "空邮箱",
|
|
191
|
+
email: "",
|
|
192
|
+
wantErr: true,
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for _, tt := range tests {
|
|
197
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
198
|
+
err := ValidateEmail(tt.email)
|
|
199
|
+
|
|
200
|
+
if tt.wantErr {
|
|
201
|
+
assert.Error(t, err, "Expected error for %s", tt.name)
|
|
202
|
+
} else {
|
|
203
|
+
assert.NoError(t, err, "Unexpected error for %s", tt.name)
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 子测试(Subtests)
|
|
211
|
+
|
|
212
|
+
**使用 t.Run 创建子测试,提供更好的组织和错误报告。**
|
|
213
|
+
|
|
214
|
+
```go
|
|
215
|
+
func TestUserService(t *testing.T) {
|
|
216
|
+
t.Run("注册用户", func(t *testing.T) {
|
|
217
|
+
t.Run("有效输入", func(t *testing.T) {
|
|
218
|
+
user, err := service.RegisterUser("user@example.com", "Pass123!")
|
|
219
|
+
assert.NoError(t, err)
|
|
220
|
+
assert.NotNil(t, user)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
t.Run("无效邮箱", func(t *testing.T) {
|
|
224
|
+
_, err := service.RegisterUser("invalid", "Pass123!")
|
|
225
|
+
assert.Error(t, err)
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
t.Run("登录用户", func(t *testing.T) {
|
|
230
|
+
// ...
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**运行特定子测试:**
|
|
236
|
+
```bash
|
|
237
|
+
go test -run TestUserService/注册用户/有效输入
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## 测试辅助函数
|
|
241
|
+
|
|
242
|
+
### Setup 和 Teardown
|
|
243
|
+
|
|
244
|
+
**使用辅助函数进行测试准备和清理。**
|
|
245
|
+
|
|
246
|
+
```go
|
|
247
|
+
// 测试级别的 setup
|
|
248
|
+
func setupTest(t *testing.T) (*UserService, *MockRepository) {
|
|
249
|
+
mockRepo := new(MockRepository)
|
|
250
|
+
service := NewUserService(mockRepo)
|
|
251
|
+
return service, mockRepo
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 包级别的 setup/teardown
|
|
255
|
+
func TestMain(m *testing.M) {
|
|
256
|
+
// 全局 setup
|
|
257
|
+
setupDatabase()
|
|
258
|
+
|
|
259
|
+
// 运行测试
|
|
260
|
+
code := m.Run()
|
|
261
|
+
|
|
262
|
+
// 全局 teardown
|
|
263
|
+
teardownDatabase()
|
|
264
|
+
|
|
265
|
+
os.Exit(code)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 使用 t.Cleanup(推荐)
|
|
269
|
+
func TestWithCleanup(t *testing.T) {
|
|
270
|
+
// 创建临时资源
|
|
271
|
+
tmpFile := createTempFile()
|
|
272
|
+
|
|
273
|
+
// 注册清理函数
|
|
274
|
+
t.Cleanup(func() {
|
|
275
|
+
os.Remove(tmpFile)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
// 测试逻辑
|
|
279
|
+
// ...
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### 测试辅助函数命名
|
|
284
|
+
|
|
285
|
+
**辅助函数应以 `test` 开头(小写),避免被识别为测试函数。**
|
|
286
|
+
|
|
287
|
+
```go
|
|
288
|
+
// ✓ 正确:辅助函数
|
|
289
|
+
func testCreateUser(t *testing.T, email string) *User {
|
|
290
|
+
t.Helper() // 标记为辅助函数,错误时显示调用者的行号
|
|
291
|
+
user, err := CreateUser(email, "Pass123!")
|
|
292
|
+
if err != nil {
|
|
293
|
+
t.Fatalf("failed to create user: %v", err)
|
|
294
|
+
}
|
|
295
|
+
return user
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// 在测试中使用
|
|
299
|
+
func TestUserOperations(t *testing.T) {
|
|
300
|
+
user := testCreateUser(t, "test@example.com")
|
|
301
|
+
// ...
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Go 特定的测试技巧
|
|
306
|
+
|
|
307
|
+
### 1. 使用接口进行 Mock
|
|
308
|
+
|
|
309
|
+
**定义接口以便于测试时替换实现。**
|
|
310
|
+
|
|
311
|
+
```go
|
|
312
|
+
// 定义接口
|
|
313
|
+
type UserRepository interface {
|
|
314
|
+
Save(user *User) error
|
|
315
|
+
FindByEmail(email string) (*User, error)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 生产实现
|
|
319
|
+
type PostgresUserRepository struct {
|
|
320
|
+
db *sql.DB
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
func (r *PostgresUserRepository) Save(user *User) error {
|
|
324
|
+
// 真实数据库操作
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 测试实现
|
|
328
|
+
type InMemoryUserRepository struct {
|
|
329
|
+
users map[string]*User
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
func (r *InMemoryUserRepository) Save(user *User) error {
|
|
333
|
+
r.users[user.Email] = user
|
|
334
|
+
return nil
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 服务依赖接口,不依赖具体实现
|
|
338
|
+
type UserService struct {
|
|
339
|
+
repo UserRepository
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
func NewUserService(repo UserRepository) *UserService {
|
|
343
|
+
return &UserService{repo: repo}
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 2. 测试并发代码
|
|
348
|
+
|
|
349
|
+
**测试涉及 goroutine 的代码。**
|
|
350
|
+
|
|
351
|
+
```go
|
|
352
|
+
func TestConcurrentAccess(t *testing.T) {
|
|
353
|
+
counter := NewSafeCounter()
|
|
354
|
+
|
|
355
|
+
// 使用 WaitGroup 等待所有 goroutine
|
|
356
|
+
var wg sync.WaitGroup
|
|
357
|
+
goroutines := 100
|
|
358
|
+
increments := 1000
|
|
359
|
+
|
|
360
|
+
for i := 0; i < goroutines; i++ {
|
|
361
|
+
wg.Add(1)
|
|
362
|
+
go func() {
|
|
363
|
+
defer wg.Done()
|
|
364
|
+
for j := 0; j < increments; j++ {
|
|
365
|
+
counter.Increment()
|
|
366
|
+
}
|
|
367
|
+
}()
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
wg.Wait()
|
|
371
|
+
|
|
372
|
+
expected := goroutines * increments
|
|
373
|
+
assert.Equal(t, expected, counter.Value())
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 检测竞态条件
|
|
377
|
+
// go test -race ./...
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 3. 测试 Panic
|
|
381
|
+
|
|
382
|
+
```go
|
|
383
|
+
func TestDivide_ByZero_Panics(t *testing.T) {
|
|
384
|
+
assert.Panics(t, func() {
|
|
385
|
+
Divide(10, 0)
|
|
386
|
+
}, "Divide by zero should panic")
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 或者使用 recover
|
|
390
|
+
func TestDivide_ByZero_Panics_Manual(t *testing.T) {
|
|
391
|
+
defer func() {
|
|
392
|
+
if r := recover(); r == nil {
|
|
393
|
+
t.Error("Expected panic but didn't get one")
|
|
394
|
+
}
|
|
395
|
+
}()
|
|
396
|
+
|
|
397
|
+
Divide(10, 0)
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### 4. 测试 HTTP Handler
|
|
402
|
+
|
|
403
|
+
```go
|
|
404
|
+
import (
|
|
405
|
+
"net/http"
|
|
406
|
+
"net/http/httptest"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
func TestUserHandler_Register(t *testing.T) {
|
|
410
|
+
// 创建请求
|
|
411
|
+
reqBody := `{"email":"user@example.com","password":"Pass123!"}`
|
|
412
|
+
req := httptest.NewRequest("POST", "/api/users/register",
|
|
413
|
+
strings.NewReader(reqBody))
|
|
414
|
+
req.Header.Set("Content-Type", "application/json")
|
|
415
|
+
|
|
416
|
+
// 创建响应记录器
|
|
417
|
+
rr := httptest.NewRecorder()
|
|
418
|
+
|
|
419
|
+
// 调用 handler
|
|
420
|
+
handler := NewUserHandler(mockService)
|
|
421
|
+
handler.ServeHTTP(rr, req)
|
|
422
|
+
|
|
423
|
+
// 断言
|
|
424
|
+
assert.Equal(t, http.StatusCreated, rr.Code)
|
|
425
|
+
assert.Contains(t, rr.Body.String(), "user@example.com")
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### 5. 使用 Testdata 目录
|
|
430
|
+
|
|
431
|
+
**Go 约定:测试数据放在 `testdata` 目录中。**
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
mypackage/
|
|
435
|
+
├── user.go
|
|
436
|
+
├── user_test.go
|
|
437
|
+
└── testdata/
|
|
438
|
+
├── valid_user.json
|
|
439
|
+
├── invalid_user.json
|
|
440
|
+
└── test_config.yaml
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
```go
|
|
444
|
+
func TestLoadUser(t *testing.T) {
|
|
445
|
+
data, err := os.ReadFile("testdata/valid_user.json")
|
|
446
|
+
assert.NoError(t, err)
|
|
447
|
+
|
|
448
|
+
var user User
|
|
449
|
+
err = json.Unmarshal(data, &user)
|
|
450
|
+
assert.NoError(t, err)
|
|
451
|
+
assert.Equal(t, "user@example.com", user.Email)
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## 基准测试(Benchmarks)
|
|
456
|
+
|
|
457
|
+
**测试性能时使用基准测试。**
|
|
458
|
+
|
|
459
|
+
```go
|
|
460
|
+
func BenchmarkValidateEmail(b *testing.B) {
|
|
461
|
+
email := "user@example.com"
|
|
462
|
+
|
|
463
|
+
b.ResetTimer() // 重置计时器,忽略 setup 时间
|
|
464
|
+
|
|
465
|
+
for i := 0; i < b.N; i++ {
|
|
466
|
+
ValidateEmail(email)
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// 运行基准测试
|
|
471
|
+
// go test -bench=. -benchmem
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## 示例测试(Examples)
|
|
475
|
+
|
|
476
|
+
**可作为文档的示例测试。**
|
|
477
|
+
|
|
478
|
+
```go
|
|
479
|
+
func ExampleRegisterUser() {
|
|
480
|
+
user, err := RegisterUser("user@example.com", "SecurePass123!")
|
|
481
|
+
if err != nil {
|
|
482
|
+
fmt.Println("Error:", err)
|
|
483
|
+
return
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
fmt.Println("User created:", user.Email)
|
|
487
|
+
// Output:
|
|
488
|
+
// User created: user@example.com
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
## Go 测试命令
|
|
493
|
+
|
|
494
|
+
### 基本命令
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
# 运行所有测试
|
|
498
|
+
go test ./...
|
|
499
|
+
|
|
500
|
+
# 运行并显示详细输出
|
|
501
|
+
go test -v ./...
|
|
502
|
+
|
|
503
|
+
# 运行特定包的测试
|
|
504
|
+
go test ./internal/user
|
|
505
|
+
|
|
506
|
+
# 运行特定测试
|
|
507
|
+
go test -run TestRegisterUser
|
|
508
|
+
|
|
509
|
+
# 运行匹配正则的测试
|
|
510
|
+
go test -run "TestRegister.*"
|
|
511
|
+
|
|
512
|
+
# 运行子测试
|
|
513
|
+
go test -run TestUserService/注册用户
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### 覆盖率
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
# 显示覆盖率
|
|
520
|
+
go test -cover ./...
|
|
521
|
+
|
|
522
|
+
# 生成覆盖率报告
|
|
523
|
+
go test -coverprofile=coverage.out ./...
|
|
524
|
+
|
|
525
|
+
# 查看覆盖率详情
|
|
526
|
+
go tool cover -func=coverage.out
|
|
527
|
+
|
|
528
|
+
# 生成 HTML 报告
|
|
529
|
+
go tool cover -html=coverage.out
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### 其他有用参数
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
# 检测竞态条件
|
|
536
|
+
go test -race ./...
|
|
537
|
+
|
|
538
|
+
# 运行基准测试
|
|
539
|
+
go test -bench=. ./...
|
|
540
|
+
|
|
541
|
+
# 查看内存分配
|
|
542
|
+
go test -bench=. -benchmem ./...
|
|
543
|
+
|
|
544
|
+
# 设置超时
|
|
545
|
+
go test -timeout 30s ./...
|
|
546
|
+
|
|
547
|
+
# 并行运行测试
|
|
548
|
+
go test -parallel 4 ./...
|
|
549
|
+
|
|
550
|
+
# 短测试模式(跳过耗时测试)
|
|
551
|
+
go test -short ./...
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## 测试组织最佳实践
|
|
555
|
+
|
|
556
|
+
### 1. 文件组织
|
|
557
|
+
|
|
558
|
+
```
|
|
559
|
+
mypackage/
|
|
560
|
+
├── user.go # 实现代码
|
|
561
|
+
├── user_test.go # 黑盒测试
|
|
562
|
+
├── user_internal_test.go # 白盒测试(如果需要)
|
|
563
|
+
├── mock_repository.go # Mock 对象
|
|
564
|
+
└── testdata/ # 测试数据
|
|
565
|
+
└── users.json
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### 2. 测试分类
|
|
569
|
+
|
|
570
|
+
**使用 build tags 分类测试。**
|
|
571
|
+
|
|
572
|
+
```go
|
|
573
|
+
// +build integration
|
|
574
|
+
|
|
575
|
+
package user_test
|
|
576
|
+
|
|
577
|
+
func TestDatabaseIntegration(t *testing.T) {
|
|
578
|
+
// 集成测试
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
```bash
|
|
583
|
+
# 只运行单元测试(默认)
|
|
584
|
+
go test ./...
|
|
585
|
+
|
|
586
|
+
# 运行集成测试
|
|
587
|
+
go test -tags=integration ./...
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### 3. 测试命名层次
|
|
591
|
+
|
|
592
|
+
```go
|
|
593
|
+
func TestUserService(t *testing.T) {
|
|
594
|
+
t.Run("Register", func(t *testing.T) {
|
|
595
|
+
t.Run("ValidInput", func(t *testing.T) {
|
|
596
|
+
t.Run("CreatesUser", func(t *testing.T) {
|
|
597
|
+
// ...
|
|
598
|
+
})
|
|
599
|
+
t.Run("ReturnsUserID", func(t *testing.T) {
|
|
600
|
+
// ...
|
|
601
|
+
})
|
|
602
|
+
})
|
|
603
|
+
t.Run("InvalidEmail", func(t *testing.T) {
|
|
604
|
+
// ...
|
|
605
|
+
})
|
|
606
|
+
})
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
## 常见陷阱
|
|
611
|
+
|
|
612
|
+
### ❌ 陷阱 1:共享状态
|
|
613
|
+
|
|
614
|
+
```go
|
|
615
|
+
// ❌ 错误:测试间共享状态
|
|
616
|
+
var testUser *User
|
|
617
|
+
|
|
618
|
+
func TestCreateUser(t *testing.T) {
|
|
619
|
+
testUser = CreateUser("test@example.com")
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
func TestUpdateUser(t *testing.T) {
|
|
623
|
+
UpdateUser(testUser, "new@example.com") // 依赖上一个测试
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ✓ 正确:每个测试独立
|
|
627
|
+
func TestUpdateUser(t *testing.T) {
|
|
628
|
+
user := CreateUser("test@example.com")
|
|
629
|
+
UpdateUser(user, "new@example.com")
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### ❌ 陷阱 2:忽略错误
|
|
634
|
+
|
|
635
|
+
```go
|
|
636
|
+
// ❌ 错误
|
|
637
|
+
func TestCreateUser(t *testing.T) {
|
|
638
|
+
user, _ := CreateUser("test@example.com") // 忽略错误
|
|
639
|
+
assert.NotNil(t, user)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ✓ 正确
|
|
643
|
+
func TestCreateUser(t *testing.T) {
|
|
644
|
+
user, err := CreateUser("test@example.com")
|
|
645
|
+
assert.NoError(t, err)
|
|
646
|
+
assert.NotNil(t, user)
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### ❌ 陷阱 3:不使用 t.Helper()
|
|
651
|
+
|
|
652
|
+
```go
|
|
653
|
+
// ❌ 错误:辅助函数不标记 Helper
|
|
654
|
+
func createTestUser(t *testing.T) *User {
|
|
655
|
+
user, err := CreateUser("test@example.com")
|
|
656
|
+
if err != nil {
|
|
657
|
+
t.Fatal(err) // 错误会显示这一行,而不是调用者
|
|
658
|
+
}
|
|
659
|
+
return user
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// ✓ 正确
|
|
663
|
+
func createTestUser(t *testing.T) *User {
|
|
664
|
+
t.Helper() // 标记为辅助函数
|
|
665
|
+
user, err := CreateUser("test@example.com")
|
|
666
|
+
if err != nil {
|
|
667
|
+
t.Fatal(err) // 错误会显示调用者的行号
|
|
668
|
+
}
|
|
669
|
+
return user
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
## 测试覆盖率目标
|
|
674
|
+
|
|
675
|
+
- **包级别**:≥ 80%
|
|
676
|
+
- **关键业务逻辑**:≥ 90%
|
|
677
|
+
- **公共 API**:100%
|
|
678
|
+
- **错误处理路径**:≥ 80%
|
|
679
|
+
|
|
680
|
+
## 质量检查清单
|
|
681
|
+
|
|
682
|
+
- [ ] 所有测试文件以 `_test.go` 结尾
|
|
683
|
+
- [ ] 测试函数以 `Test` 开头
|
|
684
|
+
- [ ] 使用表驱动测试处理多个场景
|
|
685
|
+
- [ ] 使用 `t.Run` 组织子测试
|
|
686
|
+
- [ ] 辅助函数使用 `t.Helper()`
|
|
687
|
+
- [ ] 不忽略错误返回值
|
|
688
|
+
- [ ] 测试相互独立
|
|
689
|
+
- [ ] 运行 `go test -race` 无竞态条件
|
|
690
|
+
- [ ] 覆盖率达标
|
|
691
|
+
- [ ] 测试执行快速
|