256 lines
8.6 KiB
Markdown
256 lines
8.6 KiB
Markdown
---
|
|
name: testing
|
|
description: Automatically invoked to write tests for new types, or use as testing expert advisor for guidance and recommendations. Covers unit, integration, and system tests with emphasis on in-memory dependencies. Use when creating leaf types, after refactoring, during implementation, or when testing advice is needed. Ensures 100% coverage on leaf types with public API testing.
|
|
---
|
|
|
|
# Testing Principles
|
|
|
|
Principles and patterns for writing effective Go tests.
|
|
|
|
## When to Use
|
|
|
|
### Automatic Invocation (Proactive)
|
|
- **Automatically invoked** by @linter-driven-development during Phase 2 (Implementation)
|
|
- **Automatically invoked** by @refactoring when new isolated types are created
|
|
- **Automatically invoked** by @code-designing after designing new types
|
|
- **After creating new leaf types** - Types that should have 100% unit test coverage
|
|
- **After extracting functions** during refactoring that create testable units
|
|
|
|
### Manual Invocation
|
|
- User explicitly requests tests to be written
|
|
- User asks for testing advice, recommendations, or "what to do"
|
|
- When testing strategy is unclear (table-driven vs testify suites)
|
|
- When choosing between dependency levels (in-memory vs binary vs test-containers)
|
|
- When adding tests to existing untested code
|
|
- When user needs testing expert guidance or consultation
|
|
|
|
**IMPORTANT**: This skill writes tests autonomously based on the code structure and type design, and also serves as a testing expert advisor
|
|
|
|
## Testing Philosophy
|
|
|
|
**Test only the public API**
|
|
- Use `pkg_test` package name
|
|
- Test types through their constructors
|
|
- No testing private methods/functions
|
|
|
|
**Prefer real implementations over mocks**
|
|
- Use in-memory implementations (fastest, no external deps)
|
|
- Use HTTP test servers (httptest)
|
|
- Use temp files/directories
|
|
- Test with actual dependencies when beneficial
|
|
|
|
**Coverage targets**
|
|
- Leaf types: 100% unit test coverage
|
|
- Orchestrating types: Integration tests
|
|
- Critical workflows: System tests
|
|
|
|
## Test Pyramid
|
|
|
|
Three levels of testing, each serving a specific purpose:
|
|
|
|
**Unit Tests** (Base of pyramid - most tests here)
|
|
- Test leaf types in isolation
|
|
- Fast, focused, no external dependencies
|
|
- 100% coverage target for leaf types
|
|
- Use `pkg_test` package, test public API only
|
|
|
|
**Integration Tests** (Middle - fewer than unit)
|
|
- Test seams between components
|
|
- Test workflows across package boundaries
|
|
- Use real or in-memory implementations
|
|
- Verify components work together correctly
|
|
|
|
**System Tests** (Top - fewest tests)
|
|
- Black box testing from `tests/` folder
|
|
- Test entire system via CLI/API
|
|
- Test critical end-to-end workflows
|
|
- **Strive for independence in Go** (minimize external deps)
|
|
|
|
## Reusable Test Infrastructure
|
|
|
|
Build shared test infrastructure in `internal/testutils/`:
|
|
- In-memory mock servers with DSL (HTTP, DB, file system)
|
|
- Reusable across all test levels
|
|
- Test the infrastructure itself!
|
|
- Can expose as CLI tools for manual testing
|
|
|
|
**Dependency Priority** (minimize external dependencies):
|
|
1. **In-memory** (preferred): Pure Go, httptest, in-memory DB
|
|
2. **Binary**: Standalone executable via exec.Command
|
|
3. **Test-containers**: Programmatic Docker from Go
|
|
4. **Docker-compose**: Last resort, manual testing only
|
|
|
|
Goal: System tests should be **independent in Go** when possible.
|
|
|
|
See reference.md for comprehensive testutils patterns and DSL examples.
|
|
|
|
## Workflow
|
|
|
|
### Unit Tests Workflow
|
|
|
|
**Purpose**: Test leaf types in isolation, 100% coverage target
|
|
|
|
1. **Identify leaf types** - Self-contained types with logic
|
|
2. **Choose structure** - Table-driven (simple) or testify suites (complex setup)
|
|
3. **Write in pkg_test package** - Test public API only
|
|
4. **Use in-memory implementations** - From testutils or local implementations
|
|
5. **Avoid pitfalls** - No time.Sleep, no conditionals in cases, no private method tests
|
|
|
|
**Test structure:**
|
|
- Table-driven: Separate success/error test functions (complexity = 1)
|
|
- Testify suites: Only for complex infrastructure setup (HTTP servers, DBs)
|
|
- Always use named struct fields (linter reorders fields)
|
|
|
|
See reference.md for detailed patterns and examples.
|
|
|
|
### Integration Tests Workflow
|
|
|
|
**Purpose**: Test seams between components, verify they work together
|
|
|
|
1. **Identify integration points** - Where packages/components interact
|
|
2. **Choose dependencies** - Prefer: in-memory > binary > test-containers
|
|
3. **Write tests** - In `pkg_test` or `integration_test.go` with build tags
|
|
4. **Test workflows** - Cover happy path and error scenarios across boundaries
|
|
5. **Use real or testutils implementations** - Avoid heavy mocking
|
|
|
|
**File organization:**
|
|
```go
|
|
//go:build integration
|
|
|
|
package user_test
|
|
|
|
// Test Service + Repository + real/mock dependencies
|
|
```
|
|
|
|
See reference.md for integration test patterns with dependencies.
|
|
|
|
### System Tests Workflow
|
|
|
|
**Purpose**: Black box test entire system, critical end-to-end workflows
|
|
|
|
1. **Place in tests/ folder** - At project root, separate from packages
|
|
2. **Test via CLI/API** - exec.Command for CLI, HTTP client for APIs
|
|
3. **Minimize external deps** - Prefer: in-memory mocks > binary > test-containers
|
|
4. **Strive for Go independence** - Pure Go tests, no Docker when possible
|
|
5. **Test critical workflows** - User journeys, not every edge case
|
|
|
|
**Example structure:**
|
|
```go
|
|
// tests/cli_test.go
|
|
func TestCLI_UserWorkflow(t *testing.T) {
|
|
mockAPI := testutils.NewMockServer().
|
|
OnGET("/users/1").RespondJSON(200, user).
|
|
Build() // In-memory httptest.Server
|
|
defer mockAPI.Close()
|
|
|
|
cmd := exec.Command("./myapp", "get-user", "1",
|
|
"--api-url", mockAPI.URL())
|
|
output, err := cmd.CombinedOutput()
|
|
// Assert on output
|
|
}
|
|
```
|
|
|
|
See reference.md for comprehensive system test patterns.
|
|
|
|
## Key Test Patterns
|
|
|
|
**Table-Driven Tests:**
|
|
- Separate success and error test functions (complexity = 1)
|
|
- Always use named struct fields (linter reorders fields)
|
|
- No wantErr bool pattern (adds conditionals)
|
|
|
|
**Testify Suites:**
|
|
- Only for complex infrastructure (HTTP servers, DBs, OpenTelemetry)
|
|
- SetupSuite/TearDownSuite for expensive shared setup
|
|
- SetupTest/TearDownTest for per-test isolation
|
|
|
|
**Synchronization:**
|
|
- Never use time.Sleep (flaky, slow)
|
|
- Use channels with select/timeout for async operations
|
|
- Use sync.WaitGroup for concurrent operations
|
|
|
|
See reference.md for complete patterns with code examples.
|
|
|
|
## Output Format
|
|
|
|
After writing tests:
|
|
|
|
```
|
|
✅ TESTING COMPLETE
|
|
|
|
📊 Unit Tests:
|
|
- user/user_id_test.go: 100% (4 test cases)
|
|
- user/email_test.go: 100% (6 test cases)
|
|
- user/service_test.go: 100% (8 test cases)
|
|
|
|
🔗 Integration Tests:
|
|
- user/integration_test.go: 3 workflows tested
|
|
- Dependencies: In-memory DB, httptest mock server
|
|
|
|
🎯 System Tests:
|
|
- tests/cli_test.go: 2 end-to-end workflows
|
|
- tests/api_test.go: 1 full API workflow
|
|
- Infrastructure: In-memory mocks (pure Go, no Docker)
|
|
|
|
Test Infrastructure:
|
|
- internal/testutils/httpserver: In-memory mock API with DSL
|
|
- internal/testutils/mockdb: In-memory database mock
|
|
|
|
Test Execution:
|
|
$ go test ./... # All tests
|
|
$ go test -tags=integration ./... # Include integration tests
|
|
$ go test ./tests/... # System tests only
|
|
|
|
✅ All tests pass
|
|
✅ 100% coverage on leaf types
|
|
✅ No external dependencies required
|
|
|
|
Next Steps:
|
|
1. Run linter: task lintwithfix
|
|
2. If linter fails → use @refactoring skill
|
|
3. If linter passes → use @pre-commit-review skill
|
|
```
|
|
|
|
## Key Principles
|
|
|
|
See reference.md for:
|
|
- Table-driven test patterns
|
|
- Testify suite guidelines
|
|
- Real implementations over mocks
|
|
- Synchronization techniques
|
|
- Coverage strategies
|
|
|
|
## Testing Checklist
|
|
|
|
### Unit Tests
|
|
- [ ] All unit tests in pkg_test package
|
|
- [ ] Testing public API only (no private methods)
|
|
- [ ] Table-driven tests use named struct fields
|
|
- [ ] No conditionals in test cases (complexity = 1)
|
|
- [ ] Using in-memory implementations from testutils
|
|
- [ ] No time.Sleep (using channels/waitgroups)
|
|
- [ ] Leaf types have 100% coverage
|
|
|
|
### Integration Tests
|
|
- [ ] Test seams between components
|
|
- [ ] Use in-memory or binary dependencies (avoid Docker)
|
|
- [ ] Build tags for optional execution (`//go:build integration`)
|
|
- [ ] Cover happy path and error scenarios across boundaries
|
|
- [ ] Real or testutils implementations (minimal mocking)
|
|
|
|
### System Tests
|
|
- [ ] Located in tests/ folder at project root
|
|
- [ ] Black box testing via CLI/API
|
|
- [ ] Uses in-memory testutils mocks (pure Go)
|
|
- [ ] No external dependencies (no Docker required)
|
|
- [ ] Tests critical end-to-end workflows
|
|
- [ ] Fast execution, runs in CI without setup
|
|
|
|
### Test Infrastructure
|
|
- [ ] Reusable mocks in internal/testutils/
|
|
- [ ] Test infrastructure has its own tests
|
|
- [ ] DSL provides readable test setup
|
|
- [ ] Can be exposed as CLI for manual testing
|
|
|
|
See reference.md for complete testing guidelines and examples.
|