Comprehensive testing strategies and practices for A2A-ACP development.
A2A-ACP includes 240+ comprehensive tests covering:
- Unit Tests: Individual component functionality
- Integration Tests: Component interaction testing
- Protocol Tests: A2A and Zed ACP compliance verification
- Performance Tests: Load testing and benchmarking
- End-to-End Tests: Complete workflow validation
40+ tests covering core A2A-ACP functionality:
- Task Management: Task creation, execution, cancellation
- Input-Required Workflows: Multi-turn conversation handling
- Context Persistence: Stateful conversation management
- Error Handling: Exception scenarios and recovery
- Event Emission: Push notification and event testing
Key Test Areas:
# Task lifecycle testing
test_create_task_basic()
test_execute_task_success()
test_execute_task_input_required()
test_cancel_task()
# Input-required workflow testing
test_provide_input_and_continue()
test_input_required_detection()
test_cross_protocol_state_sync()16+ tests for A2A protocol compliance:
- JSON-RPC 2.0: Request/response format validation
- Method Implementation: All A2A method testing
- Authentication: Bearer token and API key testing
- Error Handling: Protocol-compliant error responses
50+ tests for task lifecycle management:
- State Transitions: Complete state machine validation
- Context Management: Conversation persistence testing
- Message Handling: Message processing and history
- Concurrent Operations: Multi-task coordination
20+ tests for tool system functionality:
- Tool Loading: YAML configuration parsing and validation
- Parameter Validation: Type checking and requirement validation
- Tool Execution: Script execution and result handling
- Security Controls: Sandboxing and permission testing
15+ tests for protocol compliance:
- A2A AgentCard Generation: Skills and capabilities validation
- Zed ACP Tool Call Format: Tool call interception and response
- Event Emission: Protocol-compliant event structures
- Cross-Protocol State: State synchronization between protocols
10+ tests for end-to-end workflows:
- Complete Workflows: Full request → response cycles
- Event Flow: Event generation and consumption
- Error Recovery: Failure scenarios and recovery
- Performance Validation: Load and timing validation
# Run complete test suite
make test
# With coverage report
make test-coverage
# With verbose output
python -m pytest tests/ -v# Test core functionality
python -m pytest tests/test_a2a_acp_bridge.py -v
# Test A2A protocol
python -m pytest tests/test_a2a_server.py -v
# Test task management
python -m pytest tests/test_a2a_task_manager.py -v
# Test tool system
python -m pytest tests/test_tool_config.py tests/test_tool_protocol_compliance.py -v# Test single function
python -m pytest tests/test_a2a_task_manager.py::TestA2ATaskManager::test_create_task_basic -v
# Test with debugging
python -m pytest tests/test_a2a_task_manager.py::TestA2ATaskManager::test_execute_task_success -v -s
# Test input-required workflow
python -m pytest tests/test_a2a_task_manager.py -k "input_required" -v# Run tests in watch mode (during development)
python -m pytest tests/ -v --tb=short -x
# Stop on first failure for quick debugging
python -m pytest tests/test_a2a_task_manager.py -x -v
# Run only tests that failed previously
python -m pytest tests/ --lfFor isolated testing without external dependencies:
# Set dummy agent for testing
export A2A_AGENT_COMMAND="python tests/dummy_agent.py"
export A2A_AGENT_DESCRIPTION="Test agent for development"
# Run tests with dummy agent
python -m pytest tests/test_a2a_task_manager.py -vTests use an in-memory database by default:
# Test database configuration in tests
@pytest.fixture
def test_db():
# Creates temporary database for testing
# Automatically cleaned up after tests
passFollow the established testing patterns:
import pytest
from unittest.mock import AsyncMock, patch
class TestComponent:
"""Test class for specific component."""
@pytest.fixture
def component_instance(self):
"""Create component instance for testing."""
return Component()
@pytest.mark.asyncio
async def test_component_method(self, component_instance):
"""Test specific method functionality."""
# Arrange
input_data = {"test": "data"}
# Act
result = await component_instance.method(input_data)
# Assert
assert result.success == True
assert result.data == expected_data# Mock Zed ACP agent for testing
@patch('src.a2a_acp.task_manager.ZedAgentConnection')
@pytest.mark.asyncio
async def test_with_mocked_agent(self, mock_zed_connection):
"""Test with mocked Zed ACP agent."""
# Setup mock
mock_connection = AsyncMock()
mock_connection.prompt = AsyncMock(return_value={"response": "test"})
mock_zed_connection.return_value = mock_connection
# Test implementation
# ... test code ...
# Verify interactions
mock_connection.prompt.assert_called_once()All tests use pytest-asyncio for async function testing:
@pytest.mark.asyncio
async def test_async_functionality():
"""Test async component behavior."""
# Use async/await patterns
result = await async_component.method()
# Assert results
assert result is not None- Overall Coverage: >95% codebase coverage
- Critical Paths: 100% coverage for error handling and security
- Protocol Compliance: Complete coverage of A2A and Zed ACP interactions
- Integration Points: Full coverage of component interactions
# Generate coverage report
make test-coverage
# View coverage in detail
coverage report -m
# Generate HTML coverage report
coverage html
open htmlcov/index.html# Performance test structure
@pytest.mark.asyncio
async def test_concurrent_task_execution(self):
"""Test system under concurrent load."""
# Create multiple concurrent tasks
tasks = [
task_manager.execute_task(f"task_{i}", ["echo", f"test_{i}"])
for i in range(100)
]
# Execute concurrently
start_time = time.time()
results = await asyncio.gather(*tasks)
end_time = time.time()
# Validate performance
assert end_time - start_time < 30 # Should complete in <30 seconds
assert all(r.status.state == "completed" for r in results)# Memory usage validation
@pytest.mark.asyncio
async def test_memory_usage(self):
"""Test memory usage remains stable."""
import psutil
import os
process = psutil.Process(os.getpid())
initial_memory = process.memory_info().rss
# Execute memory-intensive operations
for i in range(100):
await task_manager.create_task(f"ctx_{i}", f"agent_{i}")
final_memory = process.memory_info().rss
memory_increase = final_memory - initial_memory
# Assert reasonable memory usage
assert memory_increase < 50 * 1024 * 1024 # Less than 50MB increasedef test_a2a_json_rpc_compliance(self):
"""Test A2A JSON-RPC 2.0 compliance."""
# Test request format
request = {
"jsonrpc": "2.0",
"method": "message/send",
"id": "test_001",
"params": {...}
}
# Validate against A2A specification
assert request["jsonrpc"] == "2.0"
assert "method" in request
assert "id" in requestdef test_zed_acp_protocol_compliance(self):
"""Test Zed ACP protocol compliance."""
# Test Zed ACP message format
zed_message = {
"sessionId": "sess_123",
"prompt": [
{"type": "text", "text": "Hello"}
]
}
# Validate against Zed ACP specification
assert "sessionId" in zed_message
assert "prompt" in zed_message@pytest.mark.asyncio
async def test_complete_workflow(self):
"""Test complete message → response workflow."""
# 1. Send message
message = Message(role="user", parts=[TextPart(kind="text", text="Hello")])
task = await task_manager.create_task("ctx_123", "test_agent", message)
# 2. Execute task
result = await task_manager.execute_task(task.id, ["echo", "response"])
# 3. Verify complete workflow
assert result.status.state == "completed"
assert len(result.history) >= 2 # Request + response@pytest.mark.asyncio
async def test_event_driven_workflow(self):
"""Test event-driven component interactions."""
# Setup event capture
captured_events = []
# Execute operation that generates events
await task_manager.execute_task("task_123", ["echo", "test"])
# Verify events were emitted correctly
# Event verification logic# tests/dummy_agent.py
class DummyAgentConnection:
"""Mock Zed ACP agent for testing."""
async def prompt(self, session_id, messages):
"""Return predictable responses for testing."""
return {
"stopReason": "end_turn",
"toolCalls": []
}
async def initialize(self):
"""Mock initialization."""
return {"capabilities": {"tools": True}}
# Usage in tests
export A2A_AGENT_COMMAND="python tests/dummy_agent.py"# Test data creation utilities
def create_test_message(text="Hello", role="user"):
"""Create test message with defaults."""
return Message(
role=role,
parts=[TextPart(kind="text", text=text)],
messageId=f"msg_{generate_id()}"
)
def create_test_task(context_id="ctx_123", agent_name="test_agent"):
"""Create test task with defaults."""
return Task(
id=f"task_{generate_id()}",
contextId=context_id,
status=TaskStatus(state=TaskState.SUBMITTED),
history=[],
artifacts=[],
metadata={}
)# Run with detailed output
python -m pytest tests/test_a2a_task_manager.py -v -s --tb=long
# Debug specific test
python -m pytest tests/test_a2a_task_manager.py::TestA2ATaskManager::test_execute_task_input_required -v -s --tb=long# Check test database state
sqlite3 :memory: "SELECT name FROM sqlite_master WHERE type='table';"
# Or for file-based tests
sqlite3 /tmp/test_a2a_acp.db "SELECT * FROM tasks LIMIT 5;"# Verify mock interactions
mock_connection.prompt.assert_called_once()
call_args = mock_connection.prompt.call_args
# Verify call parameters
assert call_args[0][0] == "expected_session_id" # session_id
assert call_args[0][1] == expected_messages # messages# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- run: pip install uv
- run: uv sync
- run: uv pip install -e ".[dev]"
- run: make test-coverage
- uses: codecov/codecov-action@v1# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort- One Test File per Component:
test_task_manager.pyfor task management - Descriptive Test Names:
test_create_task_with_message()nottest_create() - Arrange-Act-Assert: Clear test structure with setup, execution, verification
- Independent Tests: Tests should not depend on each other
- External Dependencies: Mock Zed ACP agents, networks, file systems
- Internal Components: Use dependency injection for testability
- State Management: Mock databases and persistent state for isolation
- Time Dependencies: Mock time-sensitive operations for predictable tests
- Proper Decorators: Use
@pytest.mark.asynciofor async tests - Await All Async Calls: Ensure all async operations are awaited
- Event Loop Management: Handle event loops properly in tests
- Concurrent Testing: Use
asyncio.gather()for concurrent operation testing
- Exception Types: Test for correct exception types and messages
- Error Recovery: Test error recovery and fallback mechanisms
- Boundary Conditions: Test edge cases and error boundaries
- Resource Cleanup: Ensure resources are cleaned up in error scenarios
"Async def functions not supported"
# Ensure pytest-asyncio is installed
pip install pytest-asyncio
# Use proper test markers
@pytest.mark.asyncio
async def test_async_function():
pass"Event loop issues"
# Mock components that create event loops
@patch('component_that_creates_loops')
def test_with_mocked_loops(self, mock_component):
pass"Database connection errors"
# Use in-memory databases for tests
@pytest.fixture
def test_db():
# Setup in-memory database
# Return connection for testing
pass"Import errors"
# Check Python path in tests
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
# Verify imports work
python -c "import a2a_acp.a2a.models; print('Imports OK')"Testing strategy documented! 🧪 For development workflow, see development-setup.md.