This document provides a comprehensive guide to Arbor's test infrastructure, including utilities, patterns, and performance optimization strategies implemented across the umbrella project.
Arbor's test infrastructure is designed for a distributed AI agent orchestration system built on Elixir/OTP. It provides comprehensive testing utilities for event sourcing, service integration, performance monitoring, and state consistency validation.
- Smart Test Dispatching: Tiered test execution with fast feedback loops
- Event Sourcing Testing: Specialized utilities for CQRS and event-driven patterns
- Service Integration: Cross-service boundary validation and contract testing
- Performance Monitoring: Real-time test performance tracking and optimization
- State Consistency: Distributed state validation across services
- PostgreSQL Testcontainers: Isolated database testing environments
test/
├── support/
│ ├── factories/ # Test data generation
│ ├── event_sourcing/ # Event sourcing utilities
│ ├── service_integration/ # Cross-service testing
│ ├── performance/ # Performance monitoring
│ └── testcontainers/ # Container management
├── unit/ # Fast unit tests
├── integration/ # Service integration tests
├── distributed/ # Multi-node tests
└── chaos/ # Chaos engineering tests
Tests are organized into execution tiers for optimal feedback:
- Fast (
@tag :fast): Unit tests, mocks only (< 100ms each) - Contract (
@tag :contract): Interface validation (< 500ms each) - Integration (
@tag :integration): Service interactions (< 2s each) - Distributed (
@tag :distributed): Multi-node scenarios (< 10s each) - Chaos (
@tag :chaos): Failure injection and recovery (< 30s each)
defmodule MyTest do
use Arbor.Persistence.FastCase
test "unit test example" do
stream_id = unique_stream_id("test")
assert String.contains?(stream_id, "test")
end
endFeatures:
- Unique stream ID generation
- Fast execution (no external dependencies)
- Isolated test environment
- Basic factory support
defmodule MyIntegrationTest do
use Arbor.Persistence.IntegrationCase
@moduletag :integration
test "database integration" do
# Testcontainer PostgreSQL available
assert {:ok, _result} = EventStore.append_events(stream_id(), events)
end
endFeatures:
- PostgreSQL Testcontainer setup/teardown
- Database migration management
- Event store integration
- Transaction rollback between tests
defmodule MyServiceTest do
use Arbor.Test.ServiceInteractionCase
test "gateway session workflow", %{interaction_context: ctx} do
# Test complete cross-service workflow
{:ok, session_id} = test_gateway_session_integration(ctx)
# Validate service boundaries
assert_state_consistency(ctx)
# Check performance
assert_performance_acceptable(ctx, :gateway_session_flow,
max_duration_ms: 1000, max_memory_mb: 5.0)
end
endFeatures:
- Service boundary validation
- Event propagation tracking
- Performance monitoring
- State consistency checking
- Error scenario testing
# Generate event streams
stream = StreamFactory.build(:user_session_stream,
session_id: "session_123",
events: [:session_created, :agent_spawned, :command_executed]
)
# Custom stream patterns
stream = StreamFactory.build(:agent_lifecycle_stream,
agent_id: "agent_456",
working_dir: "/tmp/test"
)# Generate valid commands
command = CommandFactory.spawn_agent_command(
agent_type: :llm,
working_dir: "/tmp/test",
metadata: %{session_id: "test_session"}
)
# Validate command structure
assert Command.validate(command) == :ok# Generate domain events
events = EventFactory.build_list(3, :session_events,
session_id: "session_123"
)
# Custom event sequences
events = EventFactory.build_sequence(:agent_lifecycle_events,
agent_id: "agent_456"
)use Arbor.Test.Performance.TestPerformanceMonitor
test "performance sensitive operation", %{perf_monitor: monitor} do
TestPerformanceMonitor.start_timing(monitor, :my_operation)
# ... test code ...
metrics = TestPerformanceMonitor.end_timing(monitor, :my_operation)
assert metrics.duration_ms < 100
assert metrics.memory_delta_mb < 1.0
endMetrics Tracked:
- Execution duration (microsecond precision)
- Memory usage delta
- Process count changes
- Peak memory consumption
- Resource utilization
# Set performance limits
limits = [
max_duration_ms: 500,
max_memory_mb: 10.0,
max_process_count: 5
]
# Validate performance
assert :ok = TestPerformanceMonitor.validate_performance(monitor, limits)# Track service calls
ServiceBoundaryValidator.track_call(validator, :gateway, :execute_command)
ServiceBoundaryValidator.track_call(validator, :session_manager, :create_session)
# Validate call sequence
assert ServiceBoundaryValidator.validate_call_sequence(validator, [
{:gateway, :execute_command},
{:session_manager, :create_session}
])
# Check for contract violations
assert :ok = ServiceBoundaryValidator.validate_contracts(validator)# Set event expectations
EventPropagationTracker.expect_event(tracker, :session_created, 5000)
EventPropagationTracker.expect_event(tracker, :agent_spawned, 5000)
# Record events
EventPropagationTracker.record_event(tracker, :session_created, %{}, "sessions:events")
# Validate expectations
assert :ok = EventPropagationTracker.validate_expectations(tracker)# Validate cross-service state
service_states = %{
gateway: %{active_sessions: 2, active_executions: 1},
session_manager: %{active_sessions: 2},
registry: %{registered_agents: 2}
}
assert :ok = StateConsistencyChecker.validate_consistency(checker, service_states)
# Track resource lifecycle
StateConsistencyChecker.track_resource(checker, "agent_123", :agent, %{status: :starting})
StateConsistencyChecker.update_resource_state(checker, "agent_123", :registry, %{registered: true})
StateConsistencyChecker.untrack_resource(checker, "agent_123")defmodule Arbor.Test.Testcontainers.PostgreSQLContainer do
# Automatic container lifecycle
def start_container(opts \\\\ []) do
# Starts PostgreSQL in isolated container
# Returns connection configuration
end
def stop_container(container_id) do
# Clean container shutdown
end
endFeatures:
- Isolated PostgreSQL instances per test suite
- Automatic schema migration
- Connection pooling
- Resource cleanup
- Port management
# Automatic setup in IntegrationCase
setup_all do
{:ok, container} = PostgreSQLContainer.start_container()
on_exit(fn ->
PostgreSQLContainer.stop_container(container.container_id)
end)
%{postgres_config: container.config}
end# Run tests by tier
mix test --only fast # Unit tests only
mix test --only contract # Contract tests
mix test --only integration # Integration tests
mix test --only distributed # Distributed tests
mix test --only chaos # Chaos tests
# Combined execution
mix test --exclude slow # Skip slow tests
mix test --include integration # Include integration tests# Run with performance monitoring
mix test --slowest 10 # Show 10 slowest tests
mix test.performance # Performance-focused test runmix test --coverage # Generate coverage report
mix test.all # Full test suite with quality checks
mix quality # Code quality validation-
Use Appropriate Test Tiers
- Keep unit tests in
:fasttier - Minimize
:integrationtest count - Use mocks for external dependencies
- Keep unit tests in
-
Database Optimization
- Use transactions for rollback
- Minimize schema migrations
- Pool database connections
-
Service Testing
- Mock external service calls
- Use in-memory implementations for tests
- Validate contracts without full integration
-
Memory Management
- Monitor memory usage in tests
- Clean up processes and resources
- Use lightweight test data
# Monitor test suite performance
def measure_test_suite_performance do
{time, _result} = :timer.tc(fn ->
System.cmd("mix", ["test", "--only", "fast"])
end)
IO.puts("Fast test suite: #{time / 1000}ms")
end-
Follow Naming Conventions
# Good test "gateway processes spawn_agent command successfully" test "session manager handles concurrent session creation" # Avoid test "test1" test "it works"
-
Use Descriptive Tags
@moduletag :integration @moduletag :gateway @tag :slow
-
Structure Test Data
# Use factories for consistency command = CommandFactory.spawn_agent_command(agent_type: :llm) # Avoid inline data command = %Command{type: :spawn_agent, ...}
test "handles service failures gracefully", %{interaction_context: ctx} do
# Test error propagation
result = test_error_propagation(ctx, :session_manager_failure)
# Validate clean state after error
assert :ok = StateConsistencyChecker.validate_clean_state(ctx.state_checker)
endtest "service interaction performance", %{interaction_context: ctx} do
# Measure operation
{result, metrics} = PerformanceMonitor.measure_function(ctx.performance_monitor, :service_call, fn ->
Gateway.execute_command(command, context, metadata)
end)
# Validate performance
assert metrics.duration_ms < 100
assert metrics.memory_delta_mb < 1.0
endtest "gateway command execution workflow" do
command = CommandFactory.spawn_agent_command()
# Execute through gateway
{:ok, execution_id} = Gateway.execute_command(command, %{
user_id: "test_user",
session_id: unique_stream_id("session")
}, %{timestamp: DateTime.utc_now()})
# Validate execution
assert is_binary(execution_id)
endtest "event store append and read" do
stream_id = unique_stream_id("test_stream")
events = EventFactory.build_list(3, :test_events)
# Append events
assert {:ok, _} = EventStore.append_events(stream_id, events)
# Read events back
assert {:ok, read_events} = EventStore.read_events(stream_id)
assert length(read_events) == 3
endtest "session lifecycle management" do
session_id = unique_stream_id("session")
# Create session
command = CommandFactory.create_session_command(session_id: session_id)
assert {:ok, _} = Gateway.execute_command(command, context, metadata)
# Validate session state
assert {:ok, session} = SessionManager.get_session(session_id)
assert session.status == :active
end-
Slow Test Execution
- Check for
:integrationtests without proper tagging - Verify database connection pooling
- Monitor resource cleanup
- Check for
-
Flaky Tests
- Add proper test isolation
- Use deterministic test data
- Validate async operations with timeouts
-
Memory Leaks
- Monitor process creation in tests
- Ensure proper GenServer cleanup
- Use TestPerformanceMonitor for detection
-
Database Issues
- Verify Testcontainer health
- Check migration status
- Validate connection configuration
# Enable verbose test output
mix test --trace
# Run specific test with debugging
mix test test/path/to/test.exs:42 --trace
# Monitor system resources
:observer.start()-
Update Existing Tests
# Before use ExUnit.Case # After use Arbor.Persistence.FastCase # or IntegrationCase
-
Add Performance Monitoring
# Add to existing tests use Arbor.Test.Performance.TestPerformanceMonitor test "existing test", %{perf_monitor: monitor} do # ... existing test code ... end
-
Replace Manual Data Creation
# Before command = %Command{type: :spawn_agent, ...} # After command = CommandFactory.spawn_agent_command()
- Start with
:fasttier tests usingFastCase - Add performance monitoring to critical tests
- Migrate integration tests to
IntegrationCase - Implement service interaction testing for complex workflows
- Add chaos testing for critical failure scenarios
This infrastructure provides a solid foundation for testing Arbor's distributed architecture while maintaining fast feedback loops and comprehensive validation coverage.