Testing Strategy

This document describes the testing approach for Lobster, including test categories, coverage goals, and how to run tests locally.

Test Categories

Unit Tests

Unit tests verify individual functions and methods in isolation. They should:

  • Test a single unit of functionality
  • Use minimal setup and teardown
  • Run quickly (milliseconds)
  • Not depend on external services
  • Mock external dependencies when needed

Examples:

  • URL validation logic
  • Response time calculations
  • Configuration parsing
  • Report data preparation

Integration Tests

Integration tests verify that components work together correctly. They may:

  • Test multiple components interacting
  • Use test servers for HTTP requests
  • Verify end-to-end workflows
  • Take longer to execute (seconds)

Examples:

  • Tester making real HTTP requests to test server
  • Reporter generating actual HTML/JSON files
  • robots.txt fetching and parsing
  • Authentication flows

Table-Driven Tests

Use table-driven tests for testing multiple inputs and expected outputs:

func TestValidation(t *testing.T) {
    tests := []struct {
        name     string
        input    string
        expected bool
    }{
        {"valid URL", "http://example.com", true},
        {"invalid URL", "not-a-url", false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Validate(tt.input)
            if result != tt.expected {
                t.Errorf("got %v, want %v", result, tt.expected)
            }
        })
    }
}

Coverage Goals

Target coverage by package:

  • Overall: 70%+ (currently 30.2%)
  • Critical packages: tester 86.9%, reporter TBD, validator 51.2%
  • Domain models: 100%
  • Utility packages: 90%+

Run coverage reports:

# Fast tests only (skips slow integration tests)
go test -short ./... -cover

# Full test suite (includes slow tests)
go test ./... -cover

# Detailed coverage report
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out

What to Test

  • Business logic and algorithms
  • Error handling paths (HTTP 429 retry, context cancellation, etc.)
  • Edge cases and boundary conditions
  • Public APIs and exported functions
  • Critical user workflows (crawl → test → report)

What Not to Test

  • Third-party library internals
  • Standard library behavior
  • Trivial functions

Running Tests

Run All Tests

# Fast tests only (recommended for development)
go test -short ./...

# Full test suite including slow integration tests
go test ./...

Run Tests with Coverage

go test -short ./... -cover

Run Tests with Race Detector

go test -short ./... -race

Run Specific Package

go test ./internal/tester -v

Run Specific Test

go test ./internal/tester -run TestProcessURL

Test Categories

Unit Tests

Fast, isolated tests of individual functions. See internal/tester/tester_test.go for examples.

Integration Tests

End-to-end tests with real HTTP servers. Use testing.Short() to skip in fast mode:

func TestIntegration_FullWorkflow(t *testing.T) {
    if testing.Short() {
        t.Skip("Skipping integration test in short mode")
    }
    // ... test code
}

Slow Tests

Tests taking >2 seconds (rate limiting, slow requests). Always skipped with -short flag.

Continuous Integration

CI pipeline (in progress):

  • Run fast tests: go test -short ./...
  • Check coverage
  • Run linters: golangci-lint run

Current Test Suite

Package Coverage (as of last run):

  • internal/tester: 86.9%
  • internal/config: 95.2%
  • internal/crawler: 94.9%
  • internal/validator: 51.2%
  • internal/domain: 100%
  • Overall: 30.2%

Test Breakdown:

  • Unit tests: ~50 tests
  • Integration tests: 6 tests (skipped with -short)
  • Slow tests: 2 tests (skipped with -short)

Run go test -short ./... -cover to verify current coverage.