Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:28:04 +08:00
commit bd47b24e8d
12 changed files with 5227 additions and 0 deletions

443
commands/review.md Normal file
View File

@@ -0,0 +1,443 @@
---
name: golang-development:review
description: Review Go code for idiomatic patterns, performance issues, security vulnerabilities, and common pitfalls with actionable suggestions
---
# Golang Development Review Command
Comprehensive Go code review focusing on idiomatic patterns, performance, security, and best practices.
## Usage
```bash
/golang-development:review [file-path-or-directory] [focus-area]
```
## Arguments
- `$1` - File path or directory to review (optional, defaults to current directory)
- `$2` - Focus area: `all`, `idioms`, `performance`, `security`, `concurrency`, `errors` (optional, defaults to `all`)
## Examples
```bash
# Review all files in current directory
/golang-development:review
# Review specific file
/golang-development:review internal/service/user.go
# Focus on performance issues
/golang-development:review . performance
# Focus on security
/golang-development:review ./handlers security
# Review concurrency patterns
/golang-development:review . concurrency
```
## Review Categories
### 1. Idiomatic Go (`idioms`)
**Checks:**
- Naming conventions (camelCase, capitalization)
- Error handling patterns
- Interface usage and design
- Struct composition over inheritance
- Receiver naming and types
- Exported vs. unexported identifiers
- Go proverbs adherence
**Example Issues:**
```go
// ❌ BAD: Non-idiomatic error handling
func getUser(id string) (*User, string) {
if id == "" {
return nil, "invalid ID"
}
// ...
}
// ✅ GOOD: Idiomatic error handling
func GetUser(id string) (*User, error) {
if id == "" {
return nil, fmt.Errorf("invalid ID: %s", id)
}
// ...
}
// ❌ BAD: Getter naming
func (u *User) GetName() string {
return u.name
}
// ✅ GOOD: Idiomatic getter
func (u *User) Name() string {
return u.name
}
// ❌ BAD: Setter without validation
func (u *User) SetAge(age int) {
u.age = age
}
// ✅ GOOD: Validated setter with error
func (u *User) SetAge(age int) error {
if age < 0 || age > 150 {
return fmt.Errorf("invalid age: %d", age)
}
u.age = age
return nil
}
```
### 2. Performance (`performance`)
**Checks:**
- Unnecessary allocations
- String concatenation in loops
- Slice pre-allocation
- Map pre-allocation
- Defer in loops
- Inefficient algorithms
- Memory leaks
- Goroutine leaks
**Example Issues:**
```go
// ❌ BAD: String concatenation in loop
func concat(strs []string) string {
result := ""
for _, s := range strs {
result += s // Allocates new string each time
}
return result
}
// ✅ GOOD: Use strings.Builder
func concat(strs []string) string {
var sb strings.Builder
for _, s := range strs {
sb.WriteString(s)
}
return sb.String()
}
// ❌ BAD: Growing slice
func process(n int) []int {
var result []int
for i := 0; i < n; i++ {
result = append(result, i)
}
return result
}
// ✅ GOOD: Pre-allocate
func process(n int) []int {
result := make([]int, 0, n)
for i := 0; i < n; i++ {
result = append(result, i)
}
return result
}
// ❌ BAD: Defer in tight loop
for i := 0; i < 10000; i++ {
mu.Lock()
defer mu.Unlock() // Defers accumulate
// ...
}
// ✅ GOOD: Explicit unlock
for i := 0; i < 10000; i++ {
mu.Lock()
// ...
mu.Unlock()
}
```
### 3. Security (`security`)
**Checks:**
- SQL injection vulnerabilities
- Command injection
- Path traversal
- Hardcoded credentials
- Weak cryptography
- Unsafe operations
- Input validation
- XSS vulnerabilities
**Example Issues:**
```go
// ❌ BAD: SQL injection
func getUser(db *sql.DB, username string) (*User, error) {
query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username)
return db.Query(query)
}
// ✅ GOOD: Parameterized query
func getUser(db *sql.DB, username string) (*User, error) {
query := "SELECT * FROM users WHERE username = $1"
return db.Query(query, username)
}
// ❌ BAD: Hardcoded credentials
const apiKey = "sk_live_1234567890"
// ✅ GOOD: Environment variables
apiKey := os.Getenv("API_KEY")
// ❌ BAD: Weak random
func generateToken() string {
return fmt.Sprintf("%d", rand.Int())
}
// ✅ GOOD: Cryptographically secure random
func generateToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
```
### 4. Concurrency (`concurrency`)
**Checks:**
- Race conditions
- Deadlock potential
- Missing mutex protection
- Channel misuse
- Context propagation
- Goroutine leaks
- Improper synchronization
- Lock contention
**Example Issues:**
```go
// ❌ BAD: Race condition
type Counter struct {
count int
}
func (c *Counter) Increment() {
c.count++ // Not thread-safe
}
// ✅ GOOD: Protected with mutex
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
// ❌ BAD: Goroutine leak
func fetchData(url string) <-chan Result {
ch := make(chan Result)
go func() {
// If this fails, goroutine leaks
data := fetch(url)
ch <- data
}()
return ch
}
// ✅ GOOD: Context for cancellation
func fetchData(ctx context.Context, url string) <-chan Result {
ch := make(chan Result)
go func() {
defer close(ch)
select {
case <-ctx.Done():
return
default:
data := fetch(url)
select {
case ch <- data:
case <-ctx.Done():
}
}
}()
return ch
}
```
### 5. Error Handling (`errors`)
**Checks:**
- Ignored errors
- Error wrapping
- Sentinel errors
- Custom error types
- Error messages
- Panic usage
- Recover usage
**Example Issues:**
```go
// ❌ BAD: Ignored error
file, _ := os.Open("file.txt")
// ✅ GOOD: Handle error
file, err := os.Open("file.txt")
if err != nil {
return fmt.Errorf("open file: %w", err)
}
// ❌ BAD: Lost error context
func process() error {
if err := doSomething(); err != nil {
return err
}
return nil
}
// ✅ GOOD: Wrapped error
func process() error {
if err := doSomething(); err != nil {
return fmt.Errorf("process failed: %w", err)
}
return nil
}
// ❌ BAD: Panic for normal errors
func getConfig() *Config {
cfg, err := loadConfig()
if err != nil {
panic(err) // Don't panic
}
return cfg
}
// ✅ GOOD: Return error
func getConfig() (*Config, error) {
cfg, err := loadConfig()
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
return cfg, nil
}
```
## Review Output Format
```
📝 Code Review Results
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📂 File: internal/service/user.go
⚠️ HIGH: SQL Injection Vulnerability (line 45)
├─ Issue: Unsanitized user input in SQL query
├─ Risk: Database compromise
└─ Fix: Use parameterized queries
💡 MEDIUM: Non-Idiomatic Error Handling (line 67)
├─ Issue: Returning string error instead of error type
├─ Impact: Type safety, error wrapping
└─ Suggestion: Return error type
⚡ LOW: Performance - Missing Pre-allocation (line 89)
├─ Issue: Slice growing without capacity hint
├─ Impact: Multiple allocations
└─ Optimization: make([]Type, 0, expectedSize)
✅ GOOD: Proper context propagation (line 23)
✅ GOOD: Thread-safe cache implementation (line 112)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Summary:
High: 1 | Medium: 1 | Low: 1 | Good: 2
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
## Automated Checks
The review includes automated checks using:
- `go vet` - Official Go static analysis
- `staticcheck` - Advanced static analysis
- `gosec` - Security-focused linter
- `golangci-lint` - Comprehensive linter suite
- Custom pattern matching for Go-specific issues
## Manual Review Areas
For complex code, the command performs manual review of:
- Architecture and design patterns
- API design and interfaces
- Test coverage and quality
- Documentation completeness
- Code complexity and maintainability
## Actionable Suggestions
Each issue includes:
1. **Location**: Exact file and line number
2. **Severity**: HIGH, MEDIUM, LOW
3. **Description**: What the issue is
4. **Impact**: Why it matters
5. **Fix**: How to resolve it
6. **Example**: Code snippet showing the fix
## Integration with Tools
The command can integrate with:
- GitHub PR comments
- GitLab merge request notes
- Bitbucket PR feedback
- Slack notifications
- Email reports
## Configuration
Create `.go-review.yml` in project root:
```yaml
ignore:
- vendor/
- mocks/
- ".*_test.go"
severity:
min_level: MEDIUM
focus:
- security
- performance
- concurrency
custom_rules:
- pattern: "fmt.Print"
message: "Use structured logging"
severity: LOW
```
## When to Use
Use this command:
- Before creating pull requests
- During code reviews
- After major refactoring
- When onboarding new team members
- As part of CI/CD pipeline
- When learning Go best practices
- Before production deployment
## Best Practices
The review checks compliance with:
- Effective Go guidelines
- Go Code Review Comments
- Go proverbs
- Industry best practices
- Security standards (OWASP)
- Performance optimization patterns

456
commands/scaffold.md Normal file
View File

@@ -0,0 +1,456 @@
---
name: golang-development:scaffold
description: Scaffold new Go projects with modern structure, Go modules, testing setup, CI/CD pipelines, and best practices
---
# Golang Development Scaffold Command
Create a new Go project with a production-ready structure, modern tooling, and best practices built-in.
## Usage
```bash
/golang-development:scaffold <project-name> [options]
```
## Arguments
- `$1` - Project name (required, will be used for module name)
- `$2` - Project type: `service`, `cli`, `library`, or `microservice` (optional, defaults to `service`)
- `$3` - Additional options as JSON (optional)
## Examples
```bash
# Create a basic HTTP service
/golang-development:scaffold my-api service
# Create a CLI application
/golang-development:scaffold my-tool cli
# Create a library
/golang-development:scaffold my-lib library
# Create a microservice with full features
/golang-development:scaffold user-service microservice '{"with_grpc": true, "with_db": true}'
```
## Project Structures
### Service (HTTP API)
```
my-api/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── handler/
│ │ └── health.go
│ ├── middleware/
│ │ └── logging.go
│ └── service/
│ └── user.go
├── pkg/
│ └── response/
│ └── response.go
├── api/
│ └── openapi.yaml
├── scripts/
│ └── build.sh
├── deployments/
│ ├── Dockerfile
│ └── k8s/
├── go.mod
├── go.sum
├── .gitignore
├── .golangci.yml
├── Makefile
└── README.md
```
### CLI Application
```
my-tool/
├── cmd/
│ └── my-tool/
│ └── main.go
├── internal/
│ ├── command/
│ │ ├── root.go
│ │ └── serve.go
│ └── config/
│ └── config.go
├── go.mod
├── go.sum
├── .gitignore
├── .golangci.yml
├── Makefile
└── README.md
```
### Library
```
my-lib/
├── example_test.go
├── lib.go
├── lib_test.go
├── go.mod
├── go.sum
├── .gitignore
├── .golangci.yml
├── LICENSE
└── README.md
```
### Microservice (Full Features)
```
user-service/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── domain/
│ │ └── user.go
│ ├── handler/
│ │ ├── http/
│ │ └── grpc/
│ ├── repository/
│ │ └── postgres/
│ ├── service/
│ │ └── user_service.go
│ └── infrastructure/
│ ├── database/
│ ├── cache/
│ └── messaging/
├── api/
│ ├── http/
│ │ └── openapi.yaml
│ └── grpc/
│ └── user.proto
├── pkg/
│ ├── logger/
│ ├── metrics/
│ └── tracing/
├── migrations/
│ └── 001_create_users.sql
├── deployments/
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── k8s/
├── scripts/
├── go.mod
├── go.sum
├── .gitignore
├── .golangci.yml
├── Makefile
└── README.md
```
## Generated Files
### main.go (Service)
```go
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"{{.ModuleName}}/internal/handler"
"{{.ModuleName}}/internal/middleware"
)
func main() {
// Setup router
mux := http.NewServeMux()
// Middleware
handler := middleware.Logging(
middleware.Recovery(mux),
)
// Routes
mux.HandleFunc("/health", handler.Health)
mux.HandleFunc("/ready", handler.Ready)
// Server configuration
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
server := &http.Server{
Addr: fmt.Sprintf(":%s", port),
Handler: handler,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Start server
go func() {
log.Printf("Server starting on port %s", port)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
// Graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited")
}
```
### Makefile
```makefile
.PHONY: build test lint run clean
# Variables
APP_NAME := {{.ProjectName}}
VERSION := $(shell git describe --tags --always --dirty)
BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
LDFLAGS := -ldflags "-X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME)"
# Build
build:
go build $(LDFLAGS) -o bin/$(APP_NAME) ./cmd/server
# Test
test:
go test -v -race -coverprofile=coverage.out ./...
# Coverage
coverage:
go tool cover -html=coverage.out
# Lint
lint:
golangci-lint run
# Run
run:
go run ./cmd/server
# Clean
clean:
rm -rf bin/
rm -f coverage.out
# Install tools
tools:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Docker
docker-build:
docker build -t $(APP_NAME):$(VERSION) .
docker-run:
docker run -p 8080:8080 $(APP_NAME):$(VERSION)
```
### Dockerfile
```dockerfile
# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server
# Runtime stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy binary from builder
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
```
### .golangci.yml
```yaml
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- goimports
- misspell
- gocritic
- gosec
- revive
linters-settings:
errcheck:
check-blank: true
govet:
check-shadowing: true
gofmt:
simplify: true
issues:
exclude-use-default: false
max-issues-per-linter: 0
max-same-issues: 0
```
### GitHub Actions CI (.github/workflows/ci.yml)
```yaml
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Install dependencies
run: go mod download
- name: Run tests
run: go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.out
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
build:
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Build
run: go build -v ./cmd/server
```
## Configuration Options
The command accepts a JSON configuration object:
```json
{
"with_grpc": true,
"with_db": true,
"with_redis": true,
"with_kafka": true,
"with_docker": true,
"with_k8s": true,
"with_ci": true,
"db_driver": "postgres",
"module_path": "github.com/user/project"
}
```
## Implementation Steps
1. Parse arguments and configuration
2. Create project directory structure
3. Initialize Go module
4. Generate main.go and core files
5. Create Makefile and build scripts
6. Add Dockerfile and Docker Compose
7. Generate CI/CD configuration
8. Create README with usage instructions
9. Initialize git repository
10. Run `go mod tidy`
## Features Included
- **Modern Project Structure**: Clean architecture with separation of concerns
- **HTTP Server**: Production-ready with graceful shutdown
- **Middleware**: Logging, recovery, CORS, authentication templates
- **Health Checks**: Health and readiness endpoints
- **Testing**: Test structure and examples
- **Linting**: golangci-lint configuration
- **CI/CD**: GitHub Actions workflow
- **Docker**: Multi-stage Dockerfile
- **Kubernetes**: Basic manifests (if requested)
- **Documentation**: Comprehensive README
## Post-Scaffold Steps
After scaffolding, the command will suggest:
```bash
cd {{.ProjectName}}
go mod tidy
make test
make run
```
## When to Use
Use this command to:
- Start new Go projects quickly
- Ensure consistent project structure
- Set up best practices from the start
- Include modern tooling and CI/CD
- Scaffold microservices or APIs
- Create CLI tools with proper structure

577
commands/test.md Normal file
View File

@@ -0,0 +1,577 @@
---
name: golang-development:test
description: Generate comprehensive tests including unit tests, table-driven tests, benchmarks, and examples with high coverage
---
# Golang Development Test Command
Generate comprehensive, production-ready tests for Go code including unit tests, table-driven tests, benchmarks, and examples.
## Usage
```bash
/golang-development:test <file-or-function> [test-type] [options]
```
## Arguments
- `$1` - File path or function name to test (required)
- `$2` - Test type: `unit`, `table`, `benchmark`, `integration`, `all` (optional, defaults to `unit`)
- `$3` - Options as JSON (optional)
## Examples
```bash
# Generate unit tests for a file
/golang-development:test internal/service/user.go
# Generate table-driven tests
/golang-development:test internal/service/user.go table
# Generate benchmarks
/golang-development:test internal/service/user.go benchmark
# Generate all test types
/golang-development:test internal/service/user.go all
# Generate tests with options
/golang-development:test internal/service/user.go unit '{"with_mocks": true, "coverage_target": 90}'
```
## Test Types
### 1. Unit Tests
Basic unit tests for individual functions:
```go
// Source: user.go
package service
type User struct {
ID string
Email string
Age int
}
func (u *User) IsAdult() bool {
return u.Age >= 18
}
func ValidateEmail(email string) error {
if !strings.Contains(email, "@") {
return errors.New("invalid email format")
}
return nil
}
// Generated: user_test.go
package service
import (
"testing"
)
func TestUser_IsAdult(t *testing.T) {
t.Run("adult user", func(t *testing.T) {
user := &User{Age: 25}
if !user.IsAdult() {
t.Error("expected user to be adult")
}
})
t.Run("minor user", func(t *testing.T) {
user := &User{Age: 15}
if user.IsAdult() {
t.Error("expected user to be minor")
}
})
t.Run("edge case - exactly 18", func(t *testing.T) {
user := &User{Age: 18}
if !user.IsAdult() {
t.Error("18 year old should be adult")
}
})
}
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{
name: "valid email",
email: "user@example.com",
wantErr: false,
},
{
name: "invalid email - no @",
email: "userexample.com",
wantErr: true,
},
{
name: "empty email",
email: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateEmail(tt.email)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateEmail() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
```
### 2. Table-Driven Tests
Comprehensive table-driven tests:
```go
// Source: calculator.go
package calculator
func Add(a, b int) int {
return a + b
}
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Generated: calculator_test.go
package calculator
import (
"math"
"testing"
)
func TestAdd(t *testing.T) {
tests := []struct {
name string
a int
b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -2, -3, -5},
{"mixed signs", -2, 3, 1},
{"zeros", 0, 0, 0},
{"large numbers", 1000000, 2000000, 3000000},
{"overflow scenario", math.MaxInt - 1, 1, math.MaxInt},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
func TestDivide(t *testing.T) {
tests := []struct {
name string
a float64
b float64
expected float64
expectErr bool
}{
{"normal division", 10.0, 2.0, 5.0, false},
{"division by zero", 10.0, 0.0, 0.0, true},
{"negative numbers", -10.0, 2.0, -5.0, false},
{"fractional result", 7.0, 2.0, 3.5, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Divide(tt.a, tt.b)
if tt.expectErr {
if err == nil {
t.Error("expected error but got none")
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if math.Abs(result-tt.expected) > 0.0001 {
t.Errorf("Divide(%f, %f) = %f; want %f",
tt.a, tt.b, result, tt.expected)
}
})
}
}
```
### 3. Benchmarks
Performance benchmarks:
```go
// Generated: user_bench_test.go
package service
import (
"testing"
)
func BenchmarkUser_IsAdult(b *testing.B) {
user := &User{Age: 25}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = user.IsAdult()
}
}
func BenchmarkValidateEmail(b *testing.B) {
email := "test@example.com"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ValidateEmail(email)
}
}
func BenchmarkValidateEmail_Invalid(b *testing.B) {
email := "invalid-email"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ValidateEmail(email)
}
}
// Memory allocation benchmarks
func BenchmarkStringConcatenation(b *testing.B) {
strs := []string{"hello", "world", "foo", "bar"}
b.Run("operator", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
result := ""
for _, s := range strs {
result += s
}
_ = result
}
})
b.Run("strings.Builder", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var sb strings.Builder
for _, s := range strs {
sb.WriteString(s)
}
_ = sb.String()
}
})
}
```
### 4. Integration Tests
Integration tests with external dependencies:
```go
// Generated: user_integration_test.go
// +build integration
package service
import (
"context"
"database/sql"
"testing"
_ "github.com/lib/pq"
)
func setupTestDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("postgres", "postgres://test:test@localhost/test?sslmode=disable")
if err != nil {
t.Fatalf("failed to connect to database: %v", err)
}
// Create schema
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
age INTEGER NOT NULL
)`)
if err != nil {
t.Fatalf("failed to create schema: %v", err)
}
t.Cleanup(func() {
db.Exec("DROP TABLE users")
db.Close()
})
return db
}
func TestUserRepository_Create_Integration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
db := setupTestDB(t)
repo := NewUserRepository(db)
ctx := context.Background()
user := &User{
Email: "test@example.com",
Age: 25,
}
err := repo.Create(ctx, user)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
if user.ID == "" {
t.Error("expected user ID to be set")
}
// Verify user was created
retrieved, err := repo.GetByEmail(ctx, user.Email)
if err != nil {
t.Fatalf("failed to retrieve user: %v", err)
}
if retrieved.Email != user.Email {
t.Errorf("email mismatch: got %s, want %s", retrieved.Email, user.Email)
}
}
```
### 5. Mock Generation
Generate mocks for interfaces:
```go
// Source: repository.go
package service
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
Create(ctx context.Context, user *User) error
Update(ctx context.Context, user *User) error
Delete(ctx context.Context, id string) error
}
// Generated: mocks/user_repository_mock.go
package mocks
import (
"context"
"sync"
"yourmodule/service"
)
type MockUserRepository struct {
mu sync.Mutex
GetByIDFunc func(ctx context.Context, id string) (*service.User, error)
GetByIDCalls []GetByIDCall
CreateFunc func(ctx context.Context, user *service.User) error
CreateCalls []CreateCall
UpdateFunc func(ctx context.Context, user *service.User) error
UpdateCalls []UpdateCall
DeleteFunc func(ctx context.Context, id string) error
DeleteCalls []DeleteCall
}
type GetByIDCall struct {
Ctx context.Context
ID string
}
type CreateCall struct {
Ctx context.Context
User *service.User
}
// ... more types ...
func (m *MockUserRepository) GetByID(ctx context.Context, id string) (*service.User, error) {
m.mu.Lock()
m.GetByIDCalls = append(m.GetByIDCalls, GetByIDCall{Ctx: ctx, ID: id})
m.mu.Unlock()
if m.GetByIDFunc != nil {
return m.GetByIDFunc(ctx, id)
}
return nil, nil
}
// ... more methods ...
// Usage in tests:
func TestUserService_GetUser(t *testing.T) {
mockRepo := &mocks.MockUserRepository{
GetByIDFunc: func(ctx context.Context, id string) (*service.User, error) {
return &service.User{
ID: id,
Email: "test@example.com",
Age: 25,
}, nil
},
}
svc := service.NewUserService(mockRepo)
user, err := svc.GetUser(context.Background(), "123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.ID != "123" {
t.Errorf("expected user ID 123, got %s", user.ID)
}
if len(mockRepo.GetByIDCalls) != 1 {
t.Errorf("expected 1 call to GetByID, got %d", len(mockRepo.GetByIDCalls))
}
}
```
## Test Helpers
Generate common test helpers:
```go
// Generated: testhelpers/helpers.go
package testhelpers
import (
"testing"
"time"
)
// AssertEqual checks if two values are equal
func AssertEqual(t *testing.T, got, want interface{}) {
t.Helper()
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// AssertError checks if an error occurred
func AssertError(t *testing.T, err error, wantErr bool) {
t.Helper()
if (err != nil) != wantErr {
t.Errorf("error = %v, wantErr %v", err, wantErr)
}
}
// AssertNil checks if value is nil
func AssertNil(t *testing.T, got interface{}) {
t.Helper()
if got != nil {
t.Errorf("expected nil, got %v", got)
}
}
// AssertNotNil checks if value is not nil
func AssertNotNil(t *testing.T, got interface{}) {
t.Helper()
if got == nil {
t.Error("expected non-nil value")
}
}
// Eventually retries assertion until timeout
func Eventually(t *testing.T, assertion func() bool, timeout time.Duration) {
t.Helper()
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
if assertion() {
return
}
time.Sleep(100 * time.Millisecond)
}
t.Error("assertion failed within timeout")
}
```
## Configuration Options
```json
{
"with_mocks": true,
"with_benchmarks": true,
"with_examples": true,
"coverage_target": 80,
"use_testify": false,
"parallel_tests": true,
"generate_helpers": true
}
```
## Coverage Analysis
The command includes coverage analysis:
```bash
# Run tests with coverage
go test -coverprofile=coverage.out ./...
# View coverage report
go tool cover -html=coverage.out
# Check coverage threshold
go test -cover ./... | grep "coverage:"
```
## Best Practices
Generated tests follow:
- Table-driven test patterns
- Subtests for isolation
- Test helpers for DRY code
- Proper cleanup with t.Cleanup()
- Context usage in tests
- Parallel test execution
- Comprehensive edge cases
- Clear test names
## When to Use
Use this command to:
- Generate tests for new code
- Improve test coverage
- Add missing test cases
- Create benchmark tests
- Generate integration tests
- Mock external dependencies
- Follow testing best practices