@chc880/everything-antigravity 1.0.0

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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -0
  3. package/assets/rules/common/coding-style.md +53 -0
  4. package/assets/rules/common/git-workflow.md +47 -0
  5. package/assets/rules/common/patterns.md +36 -0
  6. package/assets/rules/common/performance.md +21 -0
  7. package/assets/rules/common/security.md +34 -0
  8. package/assets/rules/common/testing.md +29 -0
  9. package/assets/rules/golang/coding-style.md +40 -0
  10. package/assets/rules/golang/patterns.md +44 -0
  11. package/assets/rules/golang/security.md +33 -0
  12. package/assets/rules/golang/testing.md +30 -0
  13. package/assets/rules/python/coding-style.md +52 -0
  14. package/assets/rules/python/patterns.md +39 -0
  15. package/assets/rules/python/security.md +30 -0
  16. package/assets/rules/python/testing.md +38 -0
  17. package/assets/rules/typescript/coding-style.md +44 -0
  18. package/assets/rules/typescript/patterns.md +50 -0
  19. package/assets/rules/typescript/security.md +27 -0
  20. package/assets/rules/typescript/testing.md +24 -0
  21. package/assets/skills/agent-guides/SKILL.md +40 -0
  22. package/assets/skills/agent-guides/references/architect.md +209 -0
  23. package/assets/skills/agent-guides/references/build-error-resolver.md +530 -0
  24. package/assets/skills/agent-guides/references/code-reviewer.md +102 -0
  25. package/assets/skills/agent-guides/references/database-reviewer.md +652 -0
  26. package/assets/skills/agent-guides/references/doc-updater.md +450 -0
  27. package/assets/skills/agent-guides/references/e2e-runner.md +795 -0
  28. package/assets/skills/agent-guides/references/go-build-resolver.md +366 -0
  29. package/assets/skills/agent-guides/references/go-reviewer.md +265 -0
  30. package/assets/skills/agent-guides/references/planner.md +117 -0
  31. package/assets/skills/agent-guides/references/python-reviewer.md +467 -0
  32. package/assets/skills/agent-guides/references/refactor-cleaner.md +304 -0
  33. package/assets/skills/agent-guides/references/security-reviewer.md +543 -0
  34. package/assets/skills/agent-guides/references/tdd-guide.md +278 -0
  35. package/assets/skills/backend-patterns/SKILL.md +587 -0
  36. package/assets/skills/clickhouse-io/SKILL.md +429 -0
  37. package/assets/skills/coding-standards/SKILL.md +520 -0
  38. package/assets/skills/cpp-testing/SKILL.md +322 -0
  39. package/assets/skills/django-patterns/SKILL.md +733 -0
  40. package/assets/skills/django-security/SKILL.md +592 -0
  41. package/assets/skills/django-tdd/SKILL.md +728 -0
  42. package/assets/skills/django-verification/SKILL.md +460 -0
  43. package/assets/skills/frontend-patterns/SKILL.md +631 -0
  44. package/assets/skills/golang-patterns/SKILL.md +673 -0
  45. package/assets/skills/golang-testing/SKILL.md +719 -0
  46. package/assets/skills/java-coding-standards/SKILL.md +138 -0
  47. package/assets/skills/jpa-patterns/SKILL.md +141 -0
  48. package/assets/skills/knowledge-management/SKILL.md +77 -0
  49. package/assets/skills/nutrient-document-processing/SKILL.md +165 -0
  50. package/assets/skills/postgres-patterns/SKILL.md +146 -0
  51. package/assets/skills/python-patterns/SKILL.md +749 -0
  52. package/assets/skills/python-testing/SKILL.md +815 -0
  53. package/assets/skills/security-hardening/SKILL.md +76 -0
  54. package/assets/skills/security-review/SKILL.md +494 -0
  55. package/assets/skills/security-review/cloud-infrastructure-security.md +361 -0
  56. package/assets/skills/springboot-patterns/SKILL.md +304 -0
  57. package/assets/skills/springboot-security/SKILL.md +119 -0
  58. package/assets/skills/springboot-tdd/SKILL.md +157 -0
  59. package/assets/skills/springboot-verification/SKILL.md +100 -0
  60. package/assets/skills/tdd-workflow/SKILL.md +409 -0
  61. package/assets/workflows/build-fix.md +50 -0
  62. package/assets/workflows/code-review.md +61 -0
  63. package/assets/workflows/e2e.md +65 -0
  64. package/assets/workflows/go-build.md +39 -0
  65. package/assets/workflows/go-review.md +44 -0
  66. package/assets/workflows/go-test.md +61 -0
  67. package/assets/workflows/plan.md +93 -0
  68. package/assets/workflows/python-review.md +95 -0
  69. package/assets/workflows/setup-pm.md +36 -0
  70. package/assets/workflows/tdd.md +75 -0
  71. package/assets/workflows/verify.md +81 -0
  72. package/bin/cli.js +69 -0
  73. package/lib/installer.js +301 -0
  74. package/package.json +34 -0
@@ -0,0 +1,719 @@
1
+ ---
2
+ name: golang-testing
3
+ description: Go testing patterns including table-driven tests, subtests, benchmarks, fuzzing, and test coverage. Follows TDD methodology with idiomatic Go practices.
4
+ ---
5
+
6
+ # Go Testing Patterns
7
+
8
+ Comprehensive Go testing patterns for writing reliable, maintainable tests following TDD methodology.
9
+
10
+ ## When to Activate
11
+
12
+ - Writing new Go functions or methods
13
+ - Adding test coverage to existing code
14
+ - Creating benchmarks for performance-critical code
15
+ - Implementing fuzz tests for input validation
16
+ - Following TDD workflow in Go projects
17
+
18
+ ## TDD Workflow for Go
19
+
20
+ ### The RED-GREEN-REFACTOR Cycle
21
+
22
+ ```
23
+ RED → Write a failing test first
24
+ GREEN → Write minimal code to pass the test
25
+ REFACTOR → Improve code while keeping tests green
26
+ REPEAT → Continue with next requirement
27
+ ```
28
+
29
+ ### Step-by-Step TDD in Go
30
+
31
+ ```go
32
+ // Step 1: Define the interface/signature
33
+ // calculator.go
34
+ package calculator
35
+
36
+ func Add(a, b int) int {
37
+ panic("not implemented") // Placeholder
38
+ }
39
+
40
+ // Step 2: Write failing test (RED)
41
+ // calculator_test.go
42
+ package calculator
43
+
44
+ import "testing"
45
+
46
+ func TestAdd(t *testing.T) {
47
+ got := Add(2, 3)
48
+ want := 5
49
+ if got != want {
50
+ t.Errorf("Add(2, 3) = %d; want %d", got, want)
51
+ }
52
+ }
53
+
54
+ // Step 3: Run test - verify FAIL
55
+ // $ go test
56
+ // --- FAIL: TestAdd (0.00s)
57
+ // panic: not implemented
58
+
59
+ // Step 4: Implement minimal code (GREEN)
60
+ func Add(a, b int) int {
61
+ return a + b
62
+ }
63
+
64
+ // Step 5: Run test - verify PASS
65
+ // $ go test
66
+ // PASS
67
+
68
+ // Step 6: Refactor if needed, verify tests still pass
69
+ ```
70
+
71
+ ## Table-Driven Tests
72
+
73
+ The standard pattern for Go tests. Enables comprehensive coverage with minimal code.
74
+
75
+ ```go
76
+ func TestAdd(t *testing.T) {
77
+ tests := []struct {
78
+ name string
79
+ a, b int
80
+ expected int
81
+ }{
82
+ {"positive numbers", 2, 3, 5},
83
+ {"negative numbers", -1, -2, -3},
84
+ {"zero values", 0, 0, 0},
85
+ {"mixed signs", -1, 1, 0},
86
+ {"large numbers", 1000000, 2000000, 3000000},
87
+ }
88
+
89
+ for _, tt := range tests {
90
+ t.Run(tt.name, func(t *testing.T) {
91
+ got := Add(tt.a, tt.b)
92
+ if got != tt.expected {
93
+ t.Errorf("Add(%d, %d) = %d; want %d",
94
+ tt.a, tt.b, got, tt.expected)
95
+ }
96
+ })
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Table-Driven Tests with Error Cases
102
+
103
+ ```go
104
+ func TestParseConfig(t *testing.T) {
105
+ tests := []struct {
106
+ name string
107
+ input string
108
+ want *Config
109
+ wantErr bool
110
+ }{
111
+ {
112
+ name: "valid config",
113
+ input: `{"host": "localhost", "port": 8080}`,
114
+ want: &Config{Host: "localhost", Port: 8080},
115
+ },
116
+ {
117
+ name: "invalid JSON",
118
+ input: `{invalid}`,
119
+ wantErr: true,
120
+ },
121
+ {
122
+ name: "empty input",
123
+ input: "",
124
+ wantErr: true,
125
+ },
126
+ {
127
+ name: "minimal config",
128
+ input: `{}`,
129
+ want: &Config{}, // Zero value config
130
+ },
131
+ }
132
+
133
+ for _, tt := range tests {
134
+ t.Run(tt.name, func(t *testing.T) {
135
+ got, err := ParseConfig(tt.input)
136
+
137
+ if tt.wantErr {
138
+ if err == nil {
139
+ t.Error("expected error, got nil")
140
+ }
141
+ return
142
+ }
143
+
144
+ if err != nil {
145
+ t.Fatalf("unexpected error: %v", err)
146
+ }
147
+
148
+ if !reflect.DeepEqual(got, tt.want) {
149
+ t.Errorf("got %+v; want %+v", got, tt.want)
150
+ }
151
+ })
152
+ }
153
+ }
154
+ ```
155
+
156
+ ## Subtests and Sub-benchmarks
157
+
158
+ ### Organizing Related Tests
159
+
160
+ ```go
161
+ func TestUser(t *testing.T) {
162
+ // Setup shared by all subtests
163
+ db := setupTestDB(t)
164
+
165
+ t.Run("Create", func(t *testing.T) {
166
+ user := &User{Name: "Alice"}
167
+ err := db.CreateUser(user)
168
+ if err != nil {
169
+ t.Fatalf("CreateUser failed: %v", err)
170
+ }
171
+ if user.ID == "" {
172
+ t.Error("expected user ID to be set")
173
+ }
174
+ })
175
+
176
+ t.Run("Get", func(t *testing.T) {
177
+ user, err := db.GetUser("alice-id")
178
+ if err != nil {
179
+ t.Fatalf("GetUser failed: %v", err)
180
+ }
181
+ if user.Name != "Alice" {
182
+ t.Errorf("got name %q; want %q", user.Name, "Alice")
183
+ }
184
+ })
185
+
186
+ t.Run("Update", func(t *testing.T) {
187
+ // ...
188
+ })
189
+
190
+ t.Run("Delete", func(t *testing.T) {
191
+ // ...
192
+ })
193
+ }
194
+ ```
195
+
196
+ ### Parallel Subtests
197
+
198
+ ```go
199
+ func TestParallel(t *testing.T) {
200
+ tests := []struct {
201
+ name string
202
+ input string
203
+ }{
204
+ {"case1", "input1"},
205
+ {"case2", "input2"},
206
+ {"case3", "input3"},
207
+ }
208
+
209
+ for _, tt := range tests {
210
+ tt := tt // Capture range variable
211
+ t.Run(tt.name, func(t *testing.T) {
212
+ t.Parallel() // Run subtests in parallel
213
+ result := Process(tt.input)
214
+ // assertions...
215
+ _ = result
216
+ })
217
+ }
218
+ }
219
+ ```
220
+
221
+ ## Test Helpers
222
+
223
+ ### Helper Functions
224
+
225
+ ```go
226
+ func setupTestDB(t *testing.T) *sql.DB {
227
+ t.Helper() // Marks this as a helper function
228
+
229
+ db, err := sql.Open("sqlite3", ":memory:")
230
+ if err != nil {
231
+ t.Fatalf("failed to open database: %v", err)
232
+ }
233
+
234
+ // Cleanup when test finishes
235
+ t.Cleanup(func() {
236
+ db.Close()
237
+ })
238
+
239
+ // Run migrations
240
+ if _, err := db.Exec(schema); err != nil {
241
+ t.Fatalf("failed to create schema: %v", err)
242
+ }
243
+
244
+ return db
245
+ }
246
+
247
+ func assertNoError(t *testing.T, err error) {
248
+ t.Helper()
249
+ if err != nil {
250
+ t.Fatalf("unexpected error: %v", err)
251
+ }
252
+ }
253
+
254
+ func assertEqual[T comparable](t *testing.T, got, want T) {
255
+ t.Helper()
256
+ if got != want {
257
+ t.Errorf("got %v; want %v", got, want)
258
+ }
259
+ }
260
+ ```
261
+
262
+ ### Temporary Files and Directories
263
+
264
+ ```go
265
+ func TestFileProcessing(t *testing.T) {
266
+ // Create temp directory - automatically cleaned up
267
+ tmpDir := t.TempDir()
268
+
269
+ // Create test file
270
+ testFile := filepath.Join(tmpDir, "test.txt")
271
+ err := os.WriteFile(testFile, []byte("test content"), 0644)
272
+ if err != nil {
273
+ t.Fatalf("failed to create test file: %v", err)
274
+ }
275
+
276
+ // Run test
277
+ result, err := ProcessFile(testFile)
278
+ if err != nil {
279
+ t.Fatalf("ProcessFile failed: %v", err)
280
+ }
281
+
282
+ // Assert...
283
+ _ = result
284
+ }
285
+ ```
286
+
287
+ ## Golden Files
288
+
289
+ Testing against expected output files stored in `testdata/`.
290
+
291
+ ```go
292
+ var update = flag.Bool("update", false, "update golden files")
293
+
294
+ func TestRender(t *testing.T) {
295
+ tests := []struct {
296
+ name string
297
+ input Template
298
+ }{
299
+ {"simple", Template{Name: "test"}},
300
+ {"complex", Template{Name: "test", Items: []string{"a", "b"}}},
301
+ }
302
+
303
+ for _, tt := range tests {
304
+ t.Run(tt.name, func(t *testing.T) {
305
+ got := Render(tt.input)
306
+
307
+ golden := filepath.Join("testdata", tt.name+".golden")
308
+
309
+ if *update {
310
+ // Update golden file: go test -update
311
+ err := os.WriteFile(golden, got, 0644)
312
+ if err != nil {
313
+ t.Fatalf("failed to update golden file: %v", err)
314
+ }
315
+ }
316
+
317
+ want, err := os.ReadFile(golden)
318
+ if err != nil {
319
+ t.Fatalf("failed to read golden file: %v", err)
320
+ }
321
+
322
+ if !bytes.Equal(got, want) {
323
+ t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want)
324
+ }
325
+ })
326
+ }
327
+ }
328
+ ```
329
+
330
+ ## Mocking with Interfaces
331
+
332
+ ### Interface-Based Mocking
333
+
334
+ ```go
335
+ // Define interface for dependencies
336
+ type UserRepository interface {
337
+ GetUser(id string) (*User, error)
338
+ SaveUser(user *User) error
339
+ }
340
+
341
+ // Production implementation
342
+ type PostgresUserRepository struct {
343
+ db *sql.DB
344
+ }
345
+
346
+ func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
347
+ // Real database query
348
+ }
349
+
350
+ // Mock implementation for tests
351
+ type MockUserRepository struct {
352
+ GetUserFunc func(id string) (*User, error)
353
+ SaveUserFunc func(user *User) error
354
+ }
355
+
356
+ func (m *MockUserRepository) GetUser(id string) (*User, error) {
357
+ return m.GetUserFunc(id)
358
+ }
359
+
360
+ func (m *MockUserRepository) SaveUser(user *User) error {
361
+ return m.SaveUserFunc(user)
362
+ }
363
+
364
+ // Test using mock
365
+ func TestUserService(t *testing.T) {
366
+ mock := &MockUserRepository{
367
+ GetUserFunc: func(id string) (*User, error) {
368
+ if id == "123" {
369
+ return &User{ID: "123", Name: "Alice"}, nil
370
+ }
371
+ return nil, ErrNotFound
372
+ },
373
+ }
374
+
375
+ service := NewUserService(mock)
376
+
377
+ user, err := service.GetUserProfile("123")
378
+ if err != nil {
379
+ t.Fatalf("unexpected error: %v", err)
380
+ }
381
+ if user.Name != "Alice" {
382
+ t.Errorf("got name %q; want %q", user.Name, "Alice")
383
+ }
384
+ }
385
+ ```
386
+
387
+ ## Benchmarks
388
+
389
+ ### Basic Benchmarks
390
+
391
+ ```go
392
+ func BenchmarkProcess(b *testing.B) {
393
+ data := generateTestData(1000)
394
+ b.ResetTimer() // Don't count setup time
395
+
396
+ for i := 0; i < b.N; i++ {
397
+ Process(data)
398
+ }
399
+ }
400
+
401
+ // Run: go test -bench=BenchmarkProcess -benchmem
402
+ // Output: BenchmarkProcess-8 10000 105234 ns/op 4096 B/op 10 allocs/op
403
+ ```
404
+
405
+ ### Benchmark with Different Sizes
406
+
407
+ ```go
408
+ func BenchmarkSort(b *testing.B) {
409
+ sizes := []int{100, 1000, 10000, 100000}
410
+
411
+ for _, size := range sizes {
412
+ b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
413
+ data := generateRandomSlice(size)
414
+ b.ResetTimer()
415
+
416
+ for i := 0; i < b.N; i++ {
417
+ // Make a copy to avoid sorting already sorted data
418
+ tmp := make([]int, len(data))
419
+ copy(tmp, data)
420
+ sort.Ints(tmp)
421
+ }
422
+ })
423
+ }
424
+ }
425
+ ```
426
+
427
+ ### Memory Allocation Benchmarks
428
+
429
+ ```go
430
+ func BenchmarkStringConcat(b *testing.B) {
431
+ parts := []string{"hello", "world", "foo", "bar", "baz"}
432
+
433
+ b.Run("plus", func(b *testing.B) {
434
+ for i := 0; i < b.N; i++ {
435
+ var s string
436
+ for _, p := range parts {
437
+ s += p
438
+ }
439
+ _ = s
440
+ }
441
+ })
442
+
443
+ b.Run("builder", func(b *testing.B) {
444
+ for i := 0; i < b.N; i++ {
445
+ var sb strings.Builder
446
+ for _, p := range parts {
447
+ sb.WriteString(p)
448
+ }
449
+ _ = sb.String()
450
+ }
451
+ })
452
+
453
+ b.Run("join", func(b *testing.B) {
454
+ for i := 0; i < b.N; i++ {
455
+ _ = strings.Join(parts, "")
456
+ }
457
+ })
458
+ }
459
+ ```
460
+
461
+ ## Fuzzing (Go 1.18+)
462
+
463
+ ### Basic Fuzz Test
464
+
465
+ ```go
466
+ func FuzzParseJSON(f *testing.F) {
467
+ // Add seed corpus
468
+ f.Add(`{"name": "test"}`)
469
+ f.Add(`{"count": 123}`)
470
+ f.Add(`[]`)
471
+ f.Add(`""`)
472
+
473
+ f.Fuzz(func(t *testing.T, input string) {
474
+ var result map[string]interface{}
475
+ err := json.Unmarshal([]byte(input), &result)
476
+
477
+ if err != nil {
478
+ // Invalid JSON is expected for random input
479
+ return
480
+ }
481
+
482
+ // If parsing succeeded, re-encoding should work
483
+ _, err = json.Marshal(result)
484
+ if err != nil {
485
+ t.Errorf("Marshal failed after successful Unmarshal: %v", err)
486
+ }
487
+ })
488
+ }
489
+
490
+ // Run: go test -fuzz=FuzzParseJSON -fuzztime=30s
491
+ ```
492
+
493
+ ### Fuzz Test with Multiple Inputs
494
+
495
+ ```go
496
+ func FuzzCompare(f *testing.F) {
497
+ f.Add("hello", "world")
498
+ f.Add("", "")
499
+ f.Add("abc", "abc")
500
+
501
+ f.Fuzz(func(t *testing.T, a, b string) {
502
+ result := Compare(a, b)
503
+
504
+ // Property: Compare(a, a) should always equal 0
505
+ if a == b && result != 0 {
506
+ t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
507
+ }
508
+
509
+ // Property: Compare(a, b) and Compare(b, a) should have opposite signs
510
+ reverse := Compare(b, a)
511
+ if (result > 0 && reverse >= 0) || (result < 0 && reverse <= 0) {
512
+ if result != 0 || reverse != 0 {
513
+ t.Errorf("Compare(%q, %q) = %d, Compare(%q, %q) = %d; inconsistent",
514
+ a, b, result, b, a, reverse)
515
+ }
516
+ }
517
+ })
518
+ }
519
+ ```
520
+
521
+ ## Test Coverage
522
+
523
+ ### Running Coverage
524
+
525
+ ```bash
526
+ # Basic coverage
527
+ go test -cover ./...
528
+
529
+ # Generate coverage profile
530
+ go test -coverprofile=coverage.out ./...
531
+
532
+ # View coverage in browser
533
+ go tool cover -html=coverage.out
534
+
535
+ # View coverage by function
536
+ go tool cover -func=coverage.out
537
+
538
+ # Coverage with race detection
539
+ go test -race -coverprofile=coverage.out ./...
540
+ ```
541
+
542
+ ### Coverage Targets
543
+
544
+ | Code Type | Target |
545
+ |-----------|--------|
546
+ | Critical business logic | 100% |
547
+ | Public APIs | 90%+ |
548
+ | General code | 80%+ |
549
+ | Generated code | Exclude |
550
+
551
+ ### Excluding Generated Code from Coverage
552
+
553
+ ```go
554
+ //go:generate mockgen -source=interface.go -destination=mock_interface.go
555
+
556
+ // In coverage profile, exclude with build tags:
557
+ // go test -cover -tags=!generate ./...
558
+ ```
559
+
560
+ ## HTTP Handler Testing
561
+
562
+ ```go
563
+ func TestHealthHandler(t *testing.T) {
564
+ // Create request
565
+ req := httptest.NewRequest(http.MethodGet, "/health", nil)
566
+ w := httptest.NewRecorder()
567
+
568
+ // Call handler
569
+ HealthHandler(w, req)
570
+
571
+ // Check response
572
+ resp := w.Result()
573
+ defer resp.Body.Close()
574
+
575
+ if resp.StatusCode != http.StatusOK {
576
+ t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK)
577
+ }
578
+
579
+ body, _ := io.ReadAll(resp.Body)
580
+ if string(body) != "OK" {
581
+ t.Errorf("got body %q; want %q", body, "OK")
582
+ }
583
+ }
584
+
585
+ func TestAPIHandler(t *testing.T) {
586
+ tests := []struct {
587
+ name string
588
+ method string
589
+ path string
590
+ body string
591
+ wantStatus int
592
+ wantBody string
593
+ }{
594
+ {
595
+ name: "get user",
596
+ method: http.MethodGet,
597
+ path: "/users/123",
598
+ wantStatus: http.StatusOK,
599
+ wantBody: `{"id":"123","name":"Alice"}`,
600
+ },
601
+ {
602
+ name: "not found",
603
+ method: http.MethodGet,
604
+ path: "/users/999",
605
+ wantStatus: http.StatusNotFound,
606
+ },
607
+ {
608
+ name: "create user",
609
+ method: http.MethodPost,
610
+ path: "/users",
611
+ body: `{"name":"Bob"}`,
612
+ wantStatus: http.StatusCreated,
613
+ },
614
+ }
615
+
616
+ handler := NewAPIHandler()
617
+
618
+ for _, tt := range tests {
619
+ t.Run(tt.name, func(t *testing.T) {
620
+ var body io.Reader
621
+ if tt.body != "" {
622
+ body = strings.NewReader(tt.body)
623
+ }
624
+
625
+ req := httptest.NewRequest(tt.method, tt.path, body)
626
+ req.Header.Set("Content-Type", "application/json")
627
+ w := httptest.NewRecorder()
628
+
629
+ handler.ServeHTTP(w, req)
630
+
631
+ if w.Code != tt.wantStatus {
632
+ t.Errorf("got status %d; want %d", w.Code, tt.wantStatus)
633
+ }
634
+
635
+ if tt.wantBody != "" && w.Body.String() != tt.wantBody {
636
+ t.Errorf("got body %q; want %q", w.Body.String(), tt.wantBody)
637
+ }
638
+ })
639
+ }
640
+ }
641
+ ```
642
+
643
+ ## Testing Commands
644
+
645
+ ```bash
646
+ # Run all tests
647
+ go test ./...
648
+
649
+ # Run tests with verbose output
650
+ go test -v ./...
651
+
652
+ # Run specific test
653
+ go test -run TestAdd ./...
654
+
655
+ # Run tests matching pattern
656
+ go test -run "TestUser/Create" ./...
657
+
658
+ # Run tests with race detector
659
+ go test -race ./...
660
+
661
+ # Run tests with coverage
662
+ go test -cover -coverprofile=coverage.out ./...
663
+
664
+ # Run short tests only
665
+ go test -short ./...
666
+
667
+ # Run tests with timeout
668
+ go test -timeout 30s ./...
669
+
670
+ # Run benchmarks
671
+ go test -bench=. -benchmem ./...
672
+
673
+ # Run fuzzing
674
+ go test -fuzz=FuzzParse -fuzztime=30s ./...
675
+
676
+ # Count test runs (for flaky test detection)
677
+ go test -count=10 ./...
678
+ ```
679
+
680
+ ## Best Practices
681
+
682
+ **DO:**
683
+ - Write tests FIRST (TDD)
684
+ - Use table-driven tests for comprehensive coverage
685
+ - Test behavior, not implementation
686
+ - Use `t.Helper()` in helper functions
687
+ - Use `t.Parallel()` for independent tests
688
+ - Clean up resources with `t.Cleanup()`
689
+ - Use meaningful test names that describe the scenario
690
+
691
+ **DON'T:**
692
+ - Test private functions directly (test through public API)
693
+ - Use `time.Sleep()` in tests (use channels or conditions)
694
+ - Ignore flaky tests (fix or remove them)
695
+ - Mock everything (prefer integration tests when possible)
696
+ - Skip error path testing
697
+
698
+ ## Integration with CI/CD
699
+
700
+ ```yaml
701
+ # GitHub Actions example
702
+ test:
703
+ runs-on: ubuntu-latest
704
+ steps:
705
+ - uses: actions/checkout@v4
706
+ - uses: actions/setup-go@v5
707
+ with:
708
+ go-version: '1.22'
709
+
710
+ - name: Run tests
711
+ run: go test -race -coverprofile=coverage.out ./...
712
+
713
+ - name: Check coverage
714
+ run: |
715
+ go tool cover -func=coverage.out | grep total | awk '{print $3}' | \
716
+ awk -F'%' '{if ($1 < 80) exit 1}'
717
+ ```
718
+
719
+ **Remember**: Tests are documentation. They show how your code is meant to be used. Write them clearly and keep them up to date.