Initial commit
This commit is contained in:
260
skills/testing/examples/test-organization.md
Normal file
260
skills/testing/examples/test-organization.md
Normal 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
|
||||
Reference in New Issue
Block a user