Comprehensive Testing Guide - beforetheshoes/Traveling-Snails GitHub Wiki
Comprehensive Testing Guide
Overview
The Traveling Snails project implements a comprehensive fail-fast testing strategy that prevents failing code from reaching production through a multi-layered approach:
Pre-commit hooks (local) โ Security Tests (CI) โ Parallel Test Execution โ Branch Protection
Testing Philosophy
Fail-Fast Strategy
Our testing approach is built on the principle that issues should be caught as early as possible in the development cycle:
- Pre-commit hooks catch 90% of issues locally before commit
- Security tests run first in CI and block everything if they fail
- Parallel test execution provides fast feedback on all test categories
- Branch protection prevents merges until all tests pass
Zero Failing Commits
No failing code ever reaches CI/CD because:
- Pre-commit hooks block commits with security violations
- SwiftLint catches code quality issues before commit
- All tests must pass before merge to main branch
- Direct pushes to main are disabled
Test Categories (87 Total Tests)
๐ Security Tests (4 tests)
Purpose: Block security violations and prevent data exposure
Location: Traveling Snails Tests/Security Tests/
Key Tests:
LoggingSecurityTests
- Detects sensitive data in logsCodebaseSecurityAuditTests
- Scans for security patternsSecurityAndValidationTests
- URL validation, input sanitizationSwiftLintIntegrationTests
- Validates security rule enforcement
What's Blocked:
print()
statements (must useLogger.shared
)- Sensitive data exposure in logging
- Unsafe error message patterns
- Hardcoded credentials or API keys
๐งช Unit Tests (15 tests)
Purpose: Core business logic and model validation
Location: Traveling Snails Tests/Unit Tests/
Coverage:
- Model tests (Trip, Activity, Lodging, Organization)
- Helper utilities and extensions
- Constants and configuration validation
- Protocol implementation testing
- Import/export functionality
๐ Integration Tests (8 tests)
Purpose: SwiftData/CloudKit integration and cross-component testing
Location: Traveling Snails Tests/Integration Tests/
Key Areas:
- SwiftData persistence operations
- CloudKit synchronization
- Import/export workflows
- Network error handling
- Dependency injection validation
โก Performance Tests (2 tests)
Purpose: Detect infinite recreation bugs and memory leaks
Location: Traveling Snails Tests/Performance Tests/
and Stress Tests/
Critical Issues Prevented:
- SwiftData infinite recreation bugs
- Memory leaks in view models
- Excessive database queries
- UI performance degradation
๐พ SwiftData Tests (8 tests)
Purpose: Data persistence, relationships, and anti-patterns
Location: Traveling Snails Tests/SwiftData Tests/
Anti-Patterns Prevented:
// โ WRONG - Causes infinite recreation
struct BadView: View {
let trips: [Trip] // Parameter passing - BLOCKED by tests
}
// โ
CORRECT - Enforced by tests
struct GoodView: View {
@Query private var trips: [Trip] // Direct query
}
๐จ UI Tests (28 tests)
Purpose: Navigation, user flows, and component behavior
Location: Traveling Snails Tests/UI Tests/
Coverage:
- Navigation patterns and state management
- Component interactions and lifecycle
- Error state handling
- Accessibility compliance
- Localization validation
โ๏ธ Settings Tests (7 tests)
Purpose: Configuration, sync, and user preferences
Location: Traveling Snails Tests/Settings Tests/
Areas Covered:
- User defaults persistence
- iCloud sync diagnostics
- App settings validation
- Data browser functionality
Pre-commit Hooks
Installation
# One-time setup
./Scripts/setup-pre-commit-hooks.sh
What Hooks Do
-
SwiftLint Security Analysis
- Scans staged Swift files only (performance optimized)
- Blocks critical security violations
- Allows warnings but blocks errors
-
Commit Message Validation
- Ensures minimum length requirements
- Detects security-related commits for extra review
Bypassing Hooks (Emergency Only)
# Only use in emergencies
git commit --no-verify -m "Emergency fix"
Local Testing
Comprehensive Test Runner
# All tests with clean output
./Scripts/run-all-tests.sh
# Specific categories
./Scripts/run-all-tests.sh --security-only
./Scripts/run-all-tests.sh --unit-only
./Scripts/run-all-tests.sh --integration-only
./Scripts/run-all-tests.sh --performance-only
# Quick runs (no dependency resolution)
./Scripts/run-all-tests.sh --quick --unit-only
# Build and test
./Scripts/run-all-tests.sh --no-clean
# Tests only (skip SwiftLint)
./Scripts/run-all-tests.sh --test-only
Test Runner Features
- Colorized output with clear success/failure indicators
- Dependency validation before running tests
- xcbeautify integration for clean, readable output
- Performance timing for each test category
- Error aggregation with helpful fix suggestions
- Build artifact cleaning for consistent test environment
CI/CD Pipeline (GitHub Actions)
Workflow Structure
The comprehensive testing workflow (.github/workflows/comprehensive-tests.yml
) implements a three-phase approach:
๐ Phase 1: Security Validation (Blocking)
- Security Tests - Must pass for pipeline to continue
- Enhanced SwiftLint - Detailed security analysis with JSON output
- Dependency Scanning - Vulnerability detection in Swift packages
- Secret Detection - Scan for hardcoded credentials
โก Phase 2: Parallel Test Execution
All jobs run in parallel after security validation passes:
- Unit Tests - Core functionality validation
- Integration Tests - SwiftData/CloudKit operations
- Performance Tests - Infinite recreation prevention
- SwiftData Tests - Data layer validation
- Build Validation - Project compilation verification
๐ก๏ธ Phase 3: Quality Gates
- Test Summary - Requires ALL tests to pass
- Artifact Collection - Test results for debugging
- Branch Protection - Blocks merges with failing tests
Workflow Benefits
- Parallel execution - Faster feedback (3-5 minutes total)
- Caching - Swift packages and build artifacts cached
- Timeouts - Jobs don't hang indefinitely
- Detailed reporting - JSON analysis of SwiftLint results
- Artifact collection - Test logs available for debugging
Branch Protection
Protection Rules
Main branch is protected with these requirements:
-
โ 8 required status checks must pass:
- Security Tests
- Unit Tests
- Integration Tests
- Performance Tests
- SwiftData Tests
- Build Validation
- Enhanced Linting
- Test Summary
-
โ Pull request workflow - Direct pushes blocked
-
โ Up-to-date requirement - Branch must be current
-
โ Review requirement - At least 1 approval needed
-
โ Stale review dismissal - New commits dismiss old reviews
-
โ Force pushes disabled - History preservation
-
โ Branch deletion blocked - Protection against accidents
Merge Process
- Create feature branch from main
- Make changes and commit (pre-commit hooks run)
- Push to GitHub (triggers CI/CD workflow)
- Create pull request
- All 8 status checks must pass
- Obtain required review(s)
- Merge to main (only after all checks pass)
Test Infrastructure
Test Base Classes
SwiftDataTestBase
Provides isolated in-memory testing for SwiftData operations:
@Suite("My SwiftData Tests")
final class MyTests: SwiftDataTestBase {
@Test("Test data operations")
func testDataOperations() async throws {
// modelContext is automatically available and isolated
let trip = Trip(name: "Test Trip")
modelContext.insert(trip)
try modelContext.save()
#expect(trip.persistentModelID != nil)
}
}
TestServiceContainer
Provides mock services for dependency injection testing:
@Suite("Service Tests")
final class ServiceTests: MockServiceTestBase {
@Test("Test with mock services")
func testServices() async throws {
// serviceContainer provides all mock services
let authService = serviceContainer.authenticationService
// Test with controlled mock behavior
}
}
Test Utilities
TestLogHandler
Captures and analyzes log output for security violations:
@Test("Ensure no sensitive data in logs")
func testLoggingSecurity() async throws {
TestLogHandler.captureOutput {
// Code that might log sensitive data
Logger.shared.info("Processing trip: \(trip.name)")
}
let sensitivePatterns = TestLogHandler.detectSensitiveData()
#expect(sensitivePatterns.isEmpty, "Found sensitive data in logs")
}
Debugging Test Failures
Local Debugging
-
Run specific test category to isolate issues:
./Scripts/run-all-tests.sh --unit-only
-
Check SwiftLint violations:
swift run swiftlint lint --config .swiftlint.yml
-
Run tests in Xcode for detailed debugging:
- Open project in Xcode
- Select test target
- Press
Cmd+U
or use Test Navigator
CI/CD Debugging
- Check workflow run details in GitHub Actions
- Download artifacts for detailed test logs
- Review specific job failures for error details
- Compare with local test results to identify environment issues
Common Issues
Pre-commit Hook Failures
- Security violations: Fix print statements, use Logger.shared
- SwiftLint errors: Run
swift run swiftlint --autocorrect
- Commit message: Ensure minimum 10 character length
CI/CD Failures
- Security tests: Fix violations in source code, not test files
- Build failures: Check for missing dependencies or Xcode version issues
- Timeout issues: May indicate infinite loops or deadlocks
Performance Considerations
Test Execution Performance
- Pre-commit hooks: 3-10 seconds (staged files only)
- Local test suite: 2-5 minutes (full test run)
- CI/CD pipeline: 3-8 minutes (parallel execution)
Optimization Strategies
- Staged file analysis - Pre-commit hooks only check changed files
- Parallel execution - Multiple test categories run simultaneously
- Build caching - Swift packages and derived data cached
- Incremental testing - Only run affected test categories during development
Best Practices
Writing Tests
- Use Swift Testing framework (
@Test
,@Suite
,#expect
) - Isolate test data with SwiftDataTestBase
- Test security implications of new features
- Mock external dependencies for reliable testing
- Test error conditions and edge cases
Maintaining Tests
- Keep tests updated with code changes
- Remove obsolete tests when features are removed
- Add tests for bug fixes to prevent regression
- Review test coverage for new features
Security Testing
- Test logging patterns for sensitive data exposure
- Validate input sanitization for all user inputs
- Test error messages don't expose internal details
- Verify authentication flows work correctly
Integration with Development Workflow
Daily Development
- Write tests first (TDD approach)
- Run relevant test categories during development
- Commit frequently - pre-commit hooks catch issues early
- Review CI/CD status before requesting reviews
Feature Development
- Create feature branch from main
- Add tests for new functionality
- Run comprehensive test suite before push
- Create PR and monitor CI/CD status
- Address any test failures before requesting review
Bug Fixes
- Write test reproducing bug (if not already covered)
- Fix the issue
- Verify test passes and bug is resolved
- Run full test suite to prevent regression
- Create PR with test coverage for the fix
This comprehensive testing strategy ensures code quality, security, and reliability while providing fast feedback to developers and preventing issues from reaching production.