# Atom Testing Guide Comprehensive testing guide for the Atom platform using property-based testing, fuzzy testing, mutation testing, and chaos engineering. ## Table of Contents 1. [Quick Start](#quick-start) 2. [Testing Philosophy](#testing-philosophy) 3. [Property-Based Testing](#property-based-testing) 4. [Fuzzy Testing](#fuzzy-testing) 5. [Mutation Testing](#mutation-testing) 6. [Chaos Engineering](#chaos-engineering) 7. [Coverage Tracking](#coverage-tracking) 8. [CI/CD Integration](#cicd-integration) 9. [Bug Discovery Workflow](#bug-discovery-workflow) --- ## Quick Start ### Run All Tests ```bash cd backend # Run all tests with coverage pytest tests/ -v --cov=core --cov=api --cov=tools --cov-report=html # Run smoke tests only (<30s) pytest tests/property_tests/invariants/ -v -m "not slow" -x # Run property tests only pytest tests/property_tests/ -v -m "property" # Run with parallel execution pytest tests/ -v -n auto ``` ### Run Specific Test Types ```bash # Property-based tests pytest tests/property_tests/ -v # Fuzzy tests (with Atheris) pytest tests/fuzzy_tests/ -v # Mutation tests (weekly) mutmut run --paths-to-mutate core/security.py --runner "pytest tests/" # Chaos tests pytest tests/chaos/ -v ``` --- ## Testing Philosophy ### Test Pyramid ``` E2E Tests (5%) ╱ ╲ / Integration \ / Tests (15%) \ / \ / Property Tests (40%) \ / \ / Unit Tests (40%) \ ___________________________________ ``` ### Coverage Targets | Module | Target | Priority | |--------|--------|----------| | Financial | 100% | P0 | | Security | 100% | P0 | | Episodes | >95% | P1 | | Multi-Agent | >95% | P1 | | API Routes | >90% | P2 | | Tools | >95% | P2 | | Models | 100% | P1 | --- ## Property-Based Testing ### What is Property-Based Testing? Traditional example-based testing: ```python def test_addition(): assert add(2, 3) == 5 ``` Property-based testing: ```python @given(st.integers(), st.integers()) def test_addition_commutative(x, y): assert add(x, y) == add(y, x) # Commutativity property ``` ### Hypothesis Strategies ```python from hypothesis import strategies as st from hypothesis import given, settings # Basic strategies st.integers(min_value=0, max_value=100) st.text(min_size=0, max_size=100) st.lists(st.integers(), min_size=0, max_size=10) st.dictionaries(st.text(), st.integers()) st.floats(min_value=0.0, max_value=1.0, allow_nan=False) # Compound strategies st.tuples(st.integers(), st.text()) st.frozensets(st.text()) st.one_of(st.integers(), st.text()) # Domain-specific strategies st.datetimes(min_value=datetime(2020, 1, 1)) st.uuids() st.emails() st.just("fixed_value") # Custom strategies def agent_maturity(): return st.sampled_from(["STUDENT", "INTERN", "SUPERVISED", "AUTONOMOUS"]) def confidence_scores(): return st.floats(min_value=0.0, max_value=1.0, allow_nan=False) ``` ### Writing Property Tests **Example: Financial Invariants** ```python import pytest from hypothesis import given, strategies as st, settings from core.financial_ops_engine import FinancialOpsEngine class TestFinancialInvariants: """Property-based tests for financial operations.""" @given( budget=st.floats(min_value=0, max_value=1000000, allow_nan=False), spend=st.floats(min_value=0, max_value=1000000, allow_nan=False) ) @settings(max_examples=200) def test_budget_guardrails_enforcement(self, budget, spend): """Test that overspend is rejected.""" engine = FinancialOpsEngine() if spend > budget: result = engine.check_budget(budget, spend) assert not result.approved assert result.reason == "Budget exceeded" else: result = engine.check_budget(budget, spend) assert result.approved @given( amounts=st.lists(st.floats(min_value=0, max_value=10000, allow_nan=False), min_size=1, max_size=100) ) @settings(max_examples=100) def test_invoice_total_calculation(self, amounts): """Test that invoice total equals sum of line items.""" engine = FinancialOpsEngine() invoice = {"line_items": [{"amount": a} for a in amounts]} total = engine.calculate_invoice_total(invoice) assert total == pytest.approx(sum(amounts), rel=1e-9) ``` ### Common Properties 1. **Round-trip properties**: serialize → deserialize should return original ```python def test_encryption_roundtrip(plaintext): encrypted = encrypt(plaintext) assert decrypt(encrypted) == plaintext ``` 2. **Idempotency**: calling function twice should have same effect ```python def test_idempotency(x): assert process(process(x)) == process(x) ``` 3. **Invariants**: properties that must always hold ```python def test_confidence_bounds(confidence): assert 0.0 <= confidence <= 1.0 ``` 4. **Ordering**: sorted list should be ordered ```python def test_sorting_ordered(xs): sorted_xs = sort(xs) assert all(sorted_xs[i] <= sorted_xs[i+1] for i in range(len(sorted_xs)-1)) ``` --- ## Property-Based Test Performance Expectations ### Why Property Tests Are Slower Than Unit Tests Property-based testing with Hypothesis follows a different performance model than traditional unit tests: | Test Type | Iterations | Target Duration | Purpose | |-----------|------------|-----------------|---------| | Unit test | 1 | <0.1s | Verify single behavior | | Property test (fast) | 50-200 examples | 5-10s | Validate simple invariants | | Property test (medium) | 50-200 examples | 10-60s | Validate complex invariants | | Property test (slow) | 50-200 examples | 60-100s | Validate system invariants | **Key Points:** 1. **max_examples=200 is by design** - Each example tests different generated inputs 2. **Per-iteration cost varies** - Simple operations: ~0.05s, Database transactions: ~1-2s 3. **Shrinking adds overhead** - When Hypothesis finds a counterexample, it shrinks to minimal case 4. **Thoroughness > Speed** - Property tests catch edge cases that unit tests miss ### Performance Tier Targets **Fast Tier (<10s):** Simple invariants, minimal setup - Example: Metric collection, data structure operations - Target: 5-10s with max_examples=200 - Actual: Analytics tests run ~4s/test with max_examples=50-100 **Medium Tier (10-60s):** Database operations, moderate complexity - Example: Transaction consistency, API contract validation - Target: 10-60s with max_examples=200 - Actual: Database atomicity tests run ~5s/test with max_examples=100 **Slow Tier (60-100s):** Complex invariants, full lifecycle - Example: Database rollback with constraints, episode creation - Target: 60-100s; exceeders should reduce max_examples to 50 for CI - Actual: Episode tests run ~15-20s/test with max_examples=200 ### Per-Iteration Cost Analysis From actual performance data: ``` test_episode_creation_after_chat: 400.41s / 200 examples = ~2.0s per iteration test_atomic_rollback_with_constraint_violation: 337.37s / 200 examples = ~1.69s per iteration test_atomic_rollback_with_foreign_key_constraint: 336.93s / 200 examples = ~1.68s per iteration ``` **This is acceptable** for comprehensive invariant testing. Each iteration tests different generated inputs to validate system invariants. ### CI Optimization For faster CI runs, Hypothesis can be configured with reduced examples: **Option 1: Environment-based configuration (recommended)** ```python # In conftest.py: from hypothesis import settings import os # CI profile: faster tests with fewer examples ci_profile = settings( max_examples=50, deadline=None, suppress_health_check=list(HealthCheck) ) # Local profile: thorough testing with more examples local_profile = settings( max_examples=200, deadline=None, suppress_health_check=[HealthCheck.too_slow] ) # Auto-select based on environment DEFAULT_PROFILE = ci_profile if os.getenv("CI") else local_profile ``` **Option 2: pytest.ini configuration** ```ini [pytest] # Hypothesis property-based testing settings # Lower max_examples for faster CI runs, higher for local thorough testing hypothesis_max_examples = 200 # Default for local development hypothesis_database = .hypothesis/ hypothesis_deadline = None # Disable per-test deadline for slow property tests hypothesis_suppress_health_check = [too_slow, filter_too_much, slow_data_generation] ``` **Usage in test files:** ```python # Property tests can use the default profile: @given(...) @settings(DEFAULT_PROFILE) # Uses CI profile in CI, local profile locally def test_something(...): ... ``` **Impact:** - Local development: 200 examples (thorough testing) - CI environment: 50 examples (faster runs, ~4x speedup) - Example: 200 examples × 1.69s/iteration = 338s → 50 examples × 1.69s/iteration = 85s ### Note on <1s Target The original <1s property test target was based on unit test assumptions. Property-based testing is fundamentally different: - Unit tests run once per test - Property tests run N times (max_examples) per test Expecting property tests to complete in <1s is like expecting 200 unit tests to complete in <1s. **See:** `backend/tests/coverage_reports/metrics/property_test_performance_analysis.md` for detailed analysis. --- ## Fuzzy Testing ### What is Fuzzy Testing? Fuzzy testing throws random, malformed, or unexpected inputs at your code to find crashes and vulnerabilities. ### Atheris Setup ```python import atheris import sys @atheris.instrument_func def test_parse_currency(data): """Fuzz test for currency parsing.""" try: # Try to parse random bytes as currency string currency_str = data.decode('utf-8', errors='ignore') result = parse_currency(currency_str) # Should not crash except Exception as e: # Expected exceptions (e.g., ValueError) if not isinstance(e, (ValueError, TypeError)): raise def main(): atheris.Setup(sys.argv, test_parse_currency) atheris.Fuzz() if __name__ == "__main__": main() ``` ### Writing Fuzz Tests **Example: Input Sanitization** ```python import atheris import sys from hypothesis import strategies as st @atheris.instrument_func def test_sanitize_input_fuzz(data): """Fuzz test for input sanitization.""" try: # Random input user_input = data.decode('utf-8', errors='ignore') # Should not crash on any input sanitized = sanitize_input(user_input) # Verify no SQL injection patterns assert "'; DROP TABLE" not in sanitized assert "