261 lines
6.0 KiB
Markdown
261 lines
6.0 KiB
Markdown
# 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
|