Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:02:42 +08:00
commit 34a2423d78
33 changed files with 12105 additions and 0 deletions

View File

@@ -0,0 +1,260 @@
# Test Organization and File Structure
## File Organization
### Basic Structure
```
user/
├── user.go
├── user_test.go # Unit tests for user.go
├── service.go
├── service_test.go # Unit tests for service.go
├── repository.go
└── repository_test.go # Unit tests for repository.go
```
### With Integration and System Tests
```
project/
├── user/
│ ├── user.go
│ ├── user_test.go # Unit tests (pkg_test)
│ ├── service.go
│ ├── service_test.go # Unit tests (pkg_test)
│ └── integration_test.go # Integration tests with //go:build integration
├── internal/
│ └── testutils/ # Reusable test infrastructure
│ ├── nats.go # In-memory NATS server
│ ├── victoria.go # Victoria Metrics binary management
│ └── httpserver/ # HTTP mock DSL
│ ├── server.go
│ └── server_test.go # Test the infrastructure!
└── tests/ # System tests (black box)
├── cli_test.go # CLI testing via exec.Command
└── api_test.go # API testing via HTTP client
```
## Package Naming
### Use `pkg_test` for Unit Tests
```go
// ✅ External package - tests public API only
package user_test
import (
"testing"
"github.com/yourorg/project/user"
)
func TestService_CreateUser(t *testing.T) {
// Test through public API
svc, _ := user.NewUserService(repo, notifier)
err := svc.CreateUser(ctx, testUser)
// ...
}
```
### Avoid Same Package Testing
```go
// ❌ Same package - can test private methods (don't do this)
package user
import "testing"
func TestInternalValidation(t *testing.T) {
// Testing private function - bad practice
result := validateEmailInternal("test@example.com")
// ...
}
```
## Build Tags for Integration Tests
### Using Build Tags
```go
//go:build integration
package user_test
import (
"context"
"testing"
"myproject/internal/testutils"
)
func TestUserService_Integration(t *testing.T) {
// Integration test with real dependencies
natsServer, _ := testutils.RunNATsServer()
defer natsServer.Shutdown()
// Test with real NATS
// ...
}
```
### Running Tests
```bash
# Run only unit tests (default - no build tags)
go test ./...
# Run unit + integration tests
go test -tags=integration ./...
# Run specific package integration tests
go test -tags=integration ./user
# Run system tests only
go test ./tests/...
# Run all tests
go test -tags=integration ./...
```
## Makefile/Taskfile Integration
### Taskfile.yml Example
```yaml
version: '3'
tasks:
test:
desc: Run unit tests
cmds:
- go test -v -race ./...
test:integration:
desc: Run integration tests
cmds:
- go test -v -race -tags=integration ./...
test:system:
desc: Run system tests
cmds:
- go test -v -race ./tests/...
test:all:
desc: Run all tests
cmds:
- task: test:integration
- task: test:system
test:coverage:
desc: Run tests with coverage
cmds:
- go test -v -race -coverprofile=coverage.out ./...
- go tool cover -html=coverage.out -o coverage.html
```
### Makefile Example
```makefile
.PHONY: test test-integration test-system test-all coverage
test:
go test -v -race ./...
test-integration:
go test -v -race -tags=integration ./...
test-system:
go test -v -race ./tests/...
test-all: test-integration test-system
coverage:
go test -v -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
```
## Test File Naming
### Unit Tests
- `*_test.go` - Standard test files
- Located next to the code being tested
- Use `pkg_test` package name
### Integration Tests
- `integration_test.go` or `*_integration_test.go`
- Use `//go:build integration` tag
- Can be in same directory or separate `integration/` folder
- Use `pkg_test` package name
### System Tests
- `*_test.go` in `tests/` directory at project root
- No build tags needed (separate directory)
- Use `tests` or `main_test` package name
## CI/CD Integration
### GitHub Actions Example
```yaml
name: Tests
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Run unit tests
run: go test -v -race ./...
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Run integration tests
run: go test -v -race -tags=integration ./...
system-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Build application
run: go build -o myapp ./cmd/myapp
- name: Run system tests
run: go test -v -race ./tests/...
```
## testutils Package Structure
```
internal/testutils/
├── nats.go # NATS in-memory server helpers
├── victoria.go # Victoria Metrics binary management
├── prometheus.go # Prometheus payload helpers
├── grpc_client_mock.go # gRPC client mock with DSL
├── jrpc_server_mock.go # JSON-RPC server mock with DSL
└── httpserver/ # HTTP mock server with DSL
├── server.go
├── server_test.go # Test the infrastructure!
├── dsl.go
└── README.md
```
## Key Principles
1. **Co-locate unit tests** - Next to the code being tested
2. **Use pkg_test package** - Forces public API testing
3. **Build tags for integration** - Keep unit tests fast by default
4. **Separate system tests** - In `tests/` directory
5. **Test your test infrastructure** - Treat testutils as production code
6. **Reusable infrastructure** - Share across all test levels