Skip to main content

Overview

Camera Workflow uses Go’s built-in testing framework with comprehensive test coverage for critical components.

Running Tests

Basic Test Run

Run all tests with verbose output:
make test
This executes:
go test -v ./...

Test with Coverage

Generate coverage reports:
make test-coverage
Generates:
  • coverage.out - Machine-readable coverage data
  • coverage.html - HTML coverage report (open in browser)
Features:
  • Race condition detection (-race flag)
  • Line-by-line coverage visualization
  • Package-level coverage statistics

View Coverage Report

After running make test-coverage:
open coverage.html  # macOS
xdg-open coverage.html  # Linux

Test Organization

Tests are located alongside the code they test:
internal/
├── checksum/
│   ├── checksum.go
│   └── checksum_test.go
├── converter/
│   ├── adaptive_limiter.go
│   ├── adaptive_limiter_test.go
│   ├── adaptive_workers.go
│   ├── adaptive_workers_test.go
│   ├── copy_mode.go
│   └── copy_mode_test.go
└── ...

Test Coverage by Package

Checksum Package

File: internal/checksum/checksum_test.go Tests:
  • xxHash64 calculation accuracy
  • File vs. reader-based hashing
  • Checksum formatting
  • Error handling
go test -v ./internal/checksum/

Converter Package

Files:
  • internal/converter/adaptive_limiter_test.go
  • internal/converter/adaptive_workers_test.go
  • internal/converter/copy_mode_test.go
Tests:
  • Adaptive worker scaling logic
  • Resource-based worker adjustment
  • Copy-only mode operations
  • Semaphore behavior
go test -v ./internal/converter/

Writing Tests

Test File Naming

Follow Go conventions:
  • Test files end with _test.go
  • Test functions start with Test
  • Example: checksum_test.go for checksum.go

Example Test Structure

package checksum_test

import (
    "testing"
    "github.com/Azilone/Camera-Workflow/internal/checksum"
)

func TestXXHash64Calculation(t *testing.T) {
    hasher := checksum.NewXXHash64Hasher()
    
    // Test with known input
    sum, err := hasher.CalculateReader(strings.NewReader("test data"))
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    
    if sum == 0 {
        t.Error("expected non-zero checksum")
    }
}

Table-Driven Tests

Use table-driven tests for multiple scenarios:
func TestGetMonthName(t *testing.T) {
    tests := []struct {
        month    int
        language string
        want     string
    }{
        {1, "en", "01-January"},
        {1, "fr", "01-Janvier"},
        {12, "es", "12-Diciembre"},
        {6, "de", "06-Juni"},
    }
    
    for _, tt := range tests {
        t.Run(fmt.Sprintf("%d_%s", tt.month, tt.language), func(t *testing.T) {
            got := utils.GetMonthName(tt.month, tt.language)
            if got != tt.want {
                t.Errorf("got %q, want %q", got, tt.want)
            }
        })
    }
}

Testing Best Practices

Avoid tests that depend on:
  • Network connections
  • File system state
  • External services (FFmpeg, ImageMagick)
Use mocks or stubs for external tools:
type MockHasher struct {
    CalculateFunc func(path string) (uint64, error)
}

func (m *MockHasher) Calculate(path string) (uint64, error) {
    if m.CalculateFunc != nil {
        return m.CalculateFunc(path)
    }
    return 0, nil
}
Always test error conditions:
func TestCalculateWithInvalidFile(t *testing.T) {
    hasher := checksum.NewXXHash64Hasher()
    
    _, err := hasher.Calculate("/nonexistent/file")
    if err == nil {
        t.Error("expected error for nonexistent file")
    }
}
Create helper functions for common setup:
func createTempFile(t *testing.T, content string) string {
    t.Helper()
    
    f, err := os.CreateTemp("", "test-*")
    if err != nil {
        t.Fatalf("failed to create temp file: %v", err)
    }
    defer f.Close()
    
    if _, err := f.WriteString(content); err != nil {
        t.Fatalf("failed to write temp file: %v", err)
    }
    
    t.Cleanup(func() { os.Remove(f.Name()) })
    return f.Name()
}
Use t.Cleanup() for resource cleanup:
func TestWithTempDir(t *testing.T) {
    dir, err := os.MkdirTemp("", "test-*")
    if err != nil {
        t.Fatal(err)
    }
    t.Cleanup(func() { os.RemoveAll(dir) })
    
    // Test code using dir
}

Integration Tests

For tests requiring FFmpeg/ImageMagick:

Skip if Dependencies Missing

func TestImageConversion(t *testing.T) {
    if err := utils.CheckDependencies(); err != nil {
        t.Skip("skipping test: missing dependencies")
    }
    
    // Test image conversion
}

Use Build Tags

Separate integration tests with build tags:
//go:build integration

package converter_test

import "testing"

func TestFullConversionPipeline(t *testing.T) {
    // Integration test requiring external tools
}
Run integration tests:
go test -tags=integration ./...

Benchmarking

Write Benchmarks

func BenchmarkXXHash64(b *testing.B) {
    data := make([]byte, 1024*1024) // 1MB
    hasher := checksum.NewXXHash64Hasher()
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        hasher.CalculateReader(bytes.NewReader(data))
    }
}

Run Benchmarks

go test -bench=. -benchmem ./internal/checksum/
Output:
BenchmarkXXHash64-8    1000    1234567 ns/op    123456 B/op    12 allocs/op

Continuous Integration

The project uses GitHub Actions for CI:
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y ffmpeg imagemagick
      
      - name: Run tests
        run: make test-coverage
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage.out

Test Coverage Goals

Critical Paths

Target: 80%+
  • Security checks
  • File integrity verification
  • Configuration validation

Business Logic

Target: 70%+
  • Conversion orchestration
  • Worker management
  • Progress tracking

Utilities

Target: 60%+
  • File handling
  • Date extraction
  • Format detection

Integration

Manual Testing
  • FFmpeg integration
  • ImageMagick integration
  • Platform-specific code

Testing Tools

Race Detector

Detect race conditions in concurrent code:
go test -race ./...
Race detector has significant overhead. Only use during testing, not production.

Test Coverage by Function

View function-level coverage:
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
Output:
github.com/Azilone/Camera-Workflow/internal/checksum/checksum.go:26:    Calculate       100.0%
github.com/Azilone/Camera-Workflow/internal/checksum/checksum.go:37:    CalculateReader 100.0%
github.com/Azilone/Camera-Workflow/internal/checksum/checksum.go:48:    FormatChecksum  100.0%

Verbose Test Output

See detailed test execution:
go test -v -run TestSpecificFunction ./internal/checksum/

Test Examples

Testing Adaptive Workers

func TestAdaptiveWorkerScaling(t *testing.T) {
    limiter := NewAdaptiveLimiter(2)
    
    // Start with 2 workers
    if limiter.CurrentLimit() != 2 {
        t.Errorf("expected 2, got %d", limiter.CurrentLimit())
    }
    
    // Scale up to 4
    limiter.SetLimit(4)
    if limiter.CurrentLimit() != 4 {
        t.Errorf("expected 4, got %d", limiter.CurrentLimit())
    }
    
    // Scale down to 1
    limiter.SetLimit(1)
    if limiter.CurrentLimit() != 1 {
        t.Errorf("expected 1, got %d", limiter.CurrentLimit())
    }
}

Testing Checksum Calculation

func TestChecksumConsistency(t *testing.T) {
    hasher := checksum.NewXXHash64Hasher()
    data := []byte("consistent test data")
    
    // Calculate multiple times
    sum1, _ := hasher.CalculateReader(bytes.NewReader(data))
    sum2, _ := hasher.CalculateReader(bytes.NewReader(data))
    
    if sum1 != sum2 {
        t.Error("checksum not consistent")
    }
}

Debugging Tests

func TestWithDebug(t *testing.T) {
    t.Logf("Debug: processing file %s", filename)
    
    result := processFile(filename)
    t.Logf("Debug: result = %+v", result)
    
    if result.Error != nil {
        t.Errorf("unexpected error: %v", result.Error)
    }
}
View debug output:
go test -v ./...

Run Specific Tests

# Run specific test
go test -run TestXXHash64 ./internal/checksum/

# Run tests matching pattern
go test -run TestAdaptive.* ./internal/converter/

Next Steps

Building

Learn how to build the project

Architecture

Understand the system design

Contributing

Contribute to the project

Build docs developers (and LLMs) love