Files
gh-buzzdan-ai-coding-rules-…/skills/testing/SKILL.md
2025-11-29 18:02:42 +08:00

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.