# Testing & Code Quality Guide This project uses comprehensive testing and static analysis to ensure code quality and reliability. ## Quick Start ```bash # Install analysis tools make install-tools # Run all checks (format, lint, test, vulnerability scan) make ci # View coverage report make coverage && open coverage.html ``` ## Testing ### Run All Tests ```bash make test ``` ### Run with Coverage ```bash make coverage # Opens coverage.html in browser ``` ### Run Specific Tests ```bash # Test a single package go test -v github.com/user/package-review/internal/inventory # Test a specific test function go test -v -run TestLoadInventory ./internal/inventory # Run with race detector (detects data races) go test -race ./... # Run benchmarks go test -bench=. -benchmem ./... ``` ### Test Organization Tests follow Go conventions: - `*_test.go` files in the same package - Table-driven tests for better coverage - Benchmarks for performance-critical code - Example tests where applicable **Coverage Targets:** - Overall: >80% - Core packages (security, inventory): >90% - Report generation: >85% ## Static Analysis ### Linting The project uses **golangci-lint** which runs multiple linters: ```bash make lint ``` **Configured Linters:** - `staticcheck` — Go vet on steroids - `gosec` — Security analysis - `revive` — Code style linting - `errcheck` — Unchecked error returns - `ineffassign` — Ineffectual assignments - `unused` — Unused code detection - `gocritic` — Advanced analysis - `cyclop` — Cyclomatic complexity - `dupl` — Code duplication detection **Configuration:** `.golangci.yml` ### Format Checking ```bash # Check formatting make fmt # This runs: go fmt ./... # Format code go vet ./... # Basic type checking ``` ### Vulnerability Scanning ```bash make vulnerability ``` This runs **govulncheck** which checks against the official Go vulnerability database. ## CI/CD Pipeline Automated checks run on: - Every push to master/main/develop - Every pull request **GitHub Actions Workflow:** `.github/workflows/ci.yml` ### Checks 1. **Tests** — Multiple Go versions (1.22, 1.23) 2. **Linting** — golangci-lint with all configured rules 3. **Vulnerabilities** — govulncheck 4. **Formatting** — go fmt + go vet 5. **Coverage** — Uploaded to Codecov ## Pre-commit Hooks Set up automatic checks before committing: ```bash pip install pre-commit pre-commit install ``` This will run: - golangci-lint - File checks (trailing whitespace, JSON validity, etc.) - Go security checks (gosec) - Spell checking **Configuration:** `.pre-commit-config.yaml` ## Code Quality Standards ### Errors All errors must be handled: ```go // ✓ Good if err != nil { return fmt.Errorf("operation failed: %w", err) } // ✗ Bad _ = doSomething() // ignoring error ``` ### Naming - Package: lowercase, concise (`inventory`, `security`) - Functions: CamelCase, descriptive (`LoadInventory`, `FilterByMinSeverity`) - Constants: UPPER_SNAKE_CASE (`MAX_RETRIES`) - Unexported: lowercase (`internal`, `helper`) ### Complexity - Max cyclomatic complexity: 10 - Max package average: 5 - Avoid deep nesting (max 3 levels) ### Comments - Exported functions should have a comment - Non-obvious logic should be commented - Keep comments up-to-date with code ```go // LoadInventory reads and parses the inventory JSON file. func LoadInventory(path string) (*Inventory, error) { // ... } ``` ## Adding Tests When adding features, add corresponding tests: ```go // internal/security/feature_test.go package security import "testing" func TestNewFeature(t *testing.T) { // Arrange input := "test data" // Act result := NewFeature(input) // Assert if result != "expected" { t.Errorf("expected 'expected', got '%s'", result) } } // Table-driven test func TestFeatureVariations(t *testing.T) { tests := []struct { name string input string want string }{ {"case1", "input1", "output1"}, {"case2", "input2", "output2"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := NewFeature(tt.input) if got != tt.want { t.Errorf("got %s, want %s", got, tt.want) } }) } } // Benchmark func BenchmarkFeature(b *testing.B) { for i := 0; i < b.N; i++ { NewFeature("test") } } ``` ## Performance Testing ```bash # Run benchmarks go test -bench=. -benchmem ./... # Compare benchmarks go test -bench=. -benchmem ./... > new.txt benchstat old.txt new.txt ``` ## Coverage Gaps Check for untested code: ```bash # Generate HTML coverage report go test -coverprofile=coverage.out ./... go tool cover -html=coverage.out # Show coverage per function go tool cover -func=coverage.out ``` ## Troubleshooting ### Linting Failures ```bash # See what golangci-lint found golangci-lint run ./... # Disable a specific check for a line (use sparingly) //nolint:gosec // This is safe because... func risky() {} ``` ### Test Failures ```bash # Run with verbose output go test -v -run TestName ./... # Run with debug output go test -v -run TestName -timeout 30s ./... 2>&1 | head -100 ``` ### Race Detector The race detector catches concurrent access issues: ```bash # Run with race detector (slower, catches data races) go test -race ./... ``` ## Documentation - **Code Comments:** Explain the "why", not the "what" - **Examples:** Add example tests (`ExampleFunction`) - **Godoc:** Auto-generated from comments at https://pkg.go.dev ```bash # View godoc locally go doc -http=:6060 # Visit http://localhost:6060 ``` ## Commit Guidelines Good commits help with testing and history: 1. **Write meaningful messages:** - Good: "Fix CVE filtering by CVSS score" - Bad: "Fix bug" 2. **Keep commits small:** - One feature or fix per commit - Easier to bisect regressions 3. **Pass CI locally:** ```bash make ci ``` ## Resources - **Go Testing:** https://golang.org/pkg/testing/ - **Golangci-lint:** https://golangci-lint.run/ - **Govulncheck:** https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck - **Table-driven tests:** https://github.com/golang/go/wiki/TableDrivenTests --- **Target Metrics:** - Test coverage: >80% - Linting: 0 warnings - Vulnerabilities: 0 known issues - Code duplications: <3%