Comprehensive testing guide for the ReasoningBank implementation.
- Overview
- Test Structure
- Running Tests
- Test Categories
- Writing New Tests
- Test Fixtures
- Mocking Strategy
- Coverage Requirements
- Continuous Integration
- Troubleshooting
The ReasoningBank test suite validates the complete implementation against the paper specifications from "ReasoningBank: Scaling Agent Self-Evolving with Reasoning Memory" (Google Cloud AI Research + UIUC, September 2025).
Test Framework: pytest Total Tests: 150+ comprehensive tests Coverage Target: ≥90% code coverage
Test Philosophy:
- Paper Compliance: Every test validates implementation matches paper specifications
- Isolation: Tests run independently without external API calls
- Comprehensiveness: Unit tests, integration tests, and end-to-end workflows
- Realistic Data: Fixtures provide realistic trajectories and memory items
tests/
├── __init__.py # Test package initialization
├── conftest.py # Shared fixtures and configuration
├── run_all_tests.py # Comprehensive test runner script
│
├── fixtures/ # Test data and mock environments
│ ├── __init__.py
│ ├── sample_trajectories.py # Realistic trajectory examples
│ ├── sample_memories.py # Pre-extracted memory items
│ ├── mock_environments.py # Simulated agent environments
│ └── test_data.py # Common test data
│
├── unit/ # Unit tests (individual components)
│ ├── __init__.py
│ ├── test_models.py # Data model tests
│ ├── test_config.py # Configuration tests
│ ├── test_judge.py # TrajectoryJudge tests
│ ├── test_extractor.py # MemoryExtractor tests
│ ├── test_retriever.py # MemoryRetriever tests
│ └── test_consolidator.py # MemoryConsolidator tests
│
├── integration/ # Integration tests (component interactions)
│ ├── __init__.py
│ ├── test_closed_loop.py # Complete learning cycle tests
│ └── test_agent.py # ReasoningBankAgent integration tests
│
├── matts/ # MaTTS (Memory-aware Test-Time Scaling) tests
│ ├── __init__.py
│ ├── test_parallel.py # Parallel scaling tests (k=3,5,7)
│ └── test_sequential.py # Sequential refinement tests (k=3,5,7)
│
└── performance/ # Performance and benchmarking tests
└── __init__.py
Run all tests:
./tests/run_all_tests.pyOr using pytest directly:
pytestUnit tests only:
pytest tests/unit/ -vIntegration tests only:
pytest tests/integration/ -vMaTTS tests only:
pytest tests/matts/ -vSpecific test file:
pytest tests/unit/test_judge.py -vSpecific test class:
pytest tests/unit/test_judge.py::TestTrajectoryJudgeBasic -vSpecific test:
pytest tests/unit/test_judge.py::TestTrajectoryJudgeBasic::test_judge_initialization -vRun only unit tests:
pytest -m unitRun only integration tests:
pytest -m integrationRun only MaTTS tests:
pytest -m mattsRun fast tests (exclude slow integration/MaTTS):
pytest -m "not integration and not matts"Run with coverage:
pytest --cov=reasoningbank --cov-report=htmlView coverage report:
open htmlcov/index.htmlCoverage summary:
pytest --cov=reasoningbank --cov-report=term-missingShow print statements:
pytest -sVery verbose:
pytest -vvShow failed tests first:
pytest --failed-firstTest individual components in isolation with mocked dependencies.
- Data model creation and validation
- Serialization (to_dict, from_dict)
- Model relationships and constraints
- Paper specification compliance
Example:
def test_memory_item_creation():
"""Test MemoryItem creation with required fields"""
item = MemoryItem(
title="Test Strategy",
description="Test description",
content="Test content explaining the strategy"
)
assert item.title == "Test Strategy"
assert item.description == "Test description"
assert item.content == "Test content explaining the strategy"- Configuration defaults match paper (Appendix A.2)
- Temperature settings: agent=0.7, judge=0.0, extractor=1.0, selector=0.0
- Memory extraction limits: max 3 per trajectory, max 5 aggregated
- Embedding settings: gemini-embedding-001 (768 dimensions)
- Preset configurations
- Validation logic
Key Test:
def test_temperature_settings_match_paper():
"""Test temperature settings match paper specifications (Appendix A.2)"""
config = ReasoningBankConfig(llm_api_key="test-key", ...)
assert config.agent_temperature == 0.7 # Balanced exploration
assert config.judge_temperature == 0.0 # Deterministic judgments
assert config.extractor_temperature == 1.0 # Diverse extraction
assert config.selector_temperature == 0.0 # Consistent selection- LLM-as-a-Judge trajectory evaluation
- Prompt structure matches Figure 9 (Appendix A.1)
- Success/failure parsing
- Confidence estimation with multiple samples
- Temperature=0.0 for deterministic judgments
- Multi-provider support (Anthropic, OpenAI, Google)
Key Test:
def test_judge_prompt_matches_paper():
"""Test judge prompt matches Figure 9 structure"""
judge = TrajectoryJudge(config)
prompt = judge._build_judge_prompt(query="What is 25 * 4?", ...)
assert "expert in evaluating the performance" in prompt
assert "three types of tasks" in prompt.lower()
assert "Information seeking" in prompt
assert "Site navigation" in prompt
assert "Content modification" in prompt- Dual-prompt approach (success vs. failure) - Figure 8
- Markdown parsing:
# Memory Item,## Title,## Description,## Content - Temperature=1.0 for diverse extraction
- Max items enforcement (3 per trajectory, 5 aggregated)
- Self-contrast extraction for MaTTS parallel
- Memory schema validation (title, 1-sentence description, 1-5 sentence content)
Key Test:
def test_dual_prompt_approach():
"""Test that success and failure use different prompts"""
extractor = MemoryExtractor(config)
success_prompt = extractor._build_success_prompt(...)
failure_prompt = extractor._build_failure_prompt(...)
# Success prompt focuses on strategies
assert "successful strategies" in success_prompt.lower()
# Failure prompt focuses on preventative lessons
assert "went wrong" in failure_prompt.lower() or "avoid" in failure_prompt.lower()- Embedding generation (OpenAI and Google)
- Cosine similarity computation
- Top-k retrieval (default k=1 from paper)
- Cache management for efficiency
- Filtered retrieval (success_only, failure_only, min_similarity)
- Embedding dimension validation (OpenAI: 1536, Google: 768)
Key Test:
def test_retrieval_ranks_by_similarity():
"""Test that retrieve ranks items by cosine similarity"""
retriever = MemoryRetriever(config)
# Create items with different similarity scores
item_high_sim = MemoryItem(title="High", description="High", content="High similarity")
item_low_sim = MemoryItem(title="Low", description="Low", content="Low similarity")
results = retriever.retrieve("Query text", [entry], k=2)
# Should be ranked: high similarity first
assert results[0].title == "High"
assert results[1].title == "Low"- Persistent JSON storage (load/save)
- Memory bank management (add, get, search, remove)
- Statistics calculation (success rate, avg items, avg steps)
- Entry filtering (success, failure)
- Import/export functionality
- Cross-session persistence
Key Test:
def test_persistence_across_sessions():
"""Test that memories persist across consolidator instances"""
# Session 1: Create and save
consolidator1 = MemoryConsolidator(config)
entry_id = consolidator1.add_from_trajectory(...)
# Session 2: Load and verify
consolidator2 = MemoryConsolidator(config)
retrieved = consolidator2.get_entry(entry_id)
assert retrieved is not None
assert len(retrieved.memory_items) > 0Test interactions between multiple components in realistic workflows.
- TestClosedLoopBasic: Judge → Extract → Consolidate workflow
- TestFullClosedLoop: Complete learning cycle (Task 1 → Learn → Task 2)
- TestMemoryInjection: Memory retrieval and agent context injection
- TestPersistenceAcrossSessions: Cross-session data persistence
- TestEndToEndWorkflow: Multi-task learning scenarios
Key Test:
def test_complete_learning_cycle():
"""Test complete cycle: Task 1 → Learn → Task 2 (with retrieval)"""
# Initialize all components
judge = TrajectoryJudge(config)
extractor = MemoryExtractor(config)
consolidator = MemoryConsolidator(config)
retriever = MemoryRetriever(config)
# Task 1: First execution (no prior memories)
task1_success = judge.judge_trajectory_success(...)
task1_memories = extractor.extract_memories(...)
entry1_id = consolidator.add_from_trajectory(...)
# Task 2: Second execution (with retrieval)
retrieved_memories = retriever.retrieve(task2_query, ...)
# Should have learned from Task 1
assert len(retrieved_memories) > 0
assert stats["total_entries"] == 1- TestReasoningBankAgentBasic: Agent initialization and configuration
- TestAgentExecution: Task execution with/without memory injection
- TestAgentMemoryIntegration: Learning from success and failure
- TestAgentWithMockEnvironment: Integration with arithmetic, search, navigation environments
- TestAgentProgressiveImprovement: Memory accumulation over tasks
- TestAgentReActFormat: ReAct format parsing and trajectory formatting
- TestEndToEndAgentWorkflow: Complete agent lifecycle with multiple tasks
Key Test:
def test_agent_improves_with_memory_accumulation():
"""Test that agent performance improves as memory accumulates"""
agent = ReasoningBankAgent(config)
# Task 1: Initial execution (no prior memories)
result1 = agent.run("Calculate 25 * 4", enable_memory_injection=False)
assert result1.success is True
# Task 2: Similar task with memory injection
result2 = agent.run("Calculate 15 * 5", enable_memory_injection=True)
# Should leverage past experience
assert result2.success is True
assert len(agent.get_memory_bank()) == 2Test Memory-aware Test-Time Scaling strategies (Section 3.3 from paper).
- TestMaTTSParallelInitialization: Configuration and setup
- TestTrajectorySelection: Best-of-n selection logic
- TestParallelSampling: k-trajectory parallel sampling (k=3,5,7)
- TestSelfContrastExtraction: Aggregated memory extraction
- TestMaTTSParallelWorkflow: Complete parallel scaling workflow
- TestMaTTSParallelIntegration: End-to-end parallel scaling
Key Test:
def test_matts_parallel_run_complete_workflow():
"""Test complete MaTTS parallel workflow with k=3"""
matts = MaTTSParallel(config, environment)
result = matts.run(query="Calculate 25 * 4", max_steps=30, k=3)
# Verify result structure
assert result.scaling_mode == "parallel"
assert result.scaling_factor == 3
assert len(result.all_trajectories) == 3
assert result.best_trajectory.success is True # Best selected
assert len(result.aggregated_memories) > 0 # Self-contrast extracted- TestMaTTSSequentialInitialization: Configuration and refinement prompts
- TestRefinementPrompts: Prompt usage matching Figure 10
- TestInitialTrajectoryExecution: First attempt execution
- TestRefinementExecution: Iterative refinement with previous trajectory context
- TestTrajectorySelection: Best trajectory selection (prefers recent, successful, fewer steps)
- TestMaTTSSequentialWorkflow: Complete sequential refinement workflow
- TestMaTTSSequentialIntegration: Progressive improvement validation
Key Test:
def test_matts_sequential_shows_progressive_improvement():
"""Test that sequential refinement shows progressive improvement"""
matts = MaTTSSequential(config, environment)
result = matts.run(query="Calculate 25 * 4", max_steps=30, k=3)
# Should have 1 initial + 3 refinements = 4 trajectories
assert len(result.all_trajectories) == 4
# Best should be most refined
assert result.best_trajectory.success is True
# Verify improvement over initial attempt
initial_steps = result.all_trajectories[0].steps_taken
final_steps = result.best_trajectory.steps_taken
improvement_pct = ((initial_steps - final_steps) / initial_steps) * 100
assert improvement_pct > 0 # Shows efficiency improvementConfiguration Fixtures:
test_config: Basic ReasoningBankConfig for testingtest_config_with_api_keys: Config with mock API keystemp_memory_bank: Temporary directory for memory storage
Mock Response Fixtures:
mock_judge_responses: LLM responses for judge (success/failure)mock_extractor_responses: LLM responses for memory extractionmock_embedding_responses: Embedding vectors for retrieval testing
Data Fixtures:
sample_successful_trajectory: Realistic successful trajectorysample_failed_trajectory: Realistic failed trajectorysample_memory_items: Pre-extracted memory items
Realistic trajectory examples covering:
- Arithmetic tasks (calculator usage)
- Search tasks (web search)
- Navigation tasks (clicking, form filling)
- Multi-step reasoning chains
- Success and failure cases
Example:
ARITHMETIC_SUCCESS_TRAJECTORY = {
"query": "Calculate 25 * 4",
"trajectory": "<think>I need to multiply 25 by 4</think>\n<action>calculate 25*4</action>\n<observation>100</observation>",
"final_state": "100",
"model_output": "Answer: 100",
"steps_taken": 3,
"success": True
}Pre-extracted memory items:
- Strategy memories (from successful trajectories)
- Preventative memories (from failed trajectories)
- Domain-specific memories (arithmetic, search, navigation)
Example:
ARITHMETIC_STRATEGY_MEMORY = MemoryItem(
title="Use Calculator for Arithmetic",
description="When asked to calculate, use the calculator action directly",
content="For arithmetic calculations, use the calculator action with proper syntax: calculate <expression>. This is more reliable than manual computation.",
success_signal=True
)Simulated agent environments:
create_arithmetic_environment(): Calculator with basic operationscreate_search_environment(): Web search with realistic resultscreate_navigation_environment(): Web navigation with clickable elements
Example:
def create_arithmetic_environment() -> MockEnvironment:
"""Create arithmetic environment for testing"""
def execute_action(action: str) -> str:
if action.lower().startswith("calculate"):
expression = action.split("calculate", 1)[1].strip()
try:
result = eval(expression) # In real env, use safe calculator
return str(result)
except:
return "Error: Invalid expression"
return "Unknown action"
return MockEnvironment(
name="arithmetic",
description="Calculator environment",
execute_action=execute_action
)All tests mock LLM calls to avoid external API dependencies and ensure deterministic results.
Pattern:
from unittest.mock import patch
def test_with_mocked_llm():
"""Test with mocked LLM call"""
agent = ReasoningBankAgent(config)
# Mock the LLM response
mock_response = "<think>Reasoning</think>\n<action>Answer: 42</action>"
with patch.object(agent, '_call_agent_llm', return_value=mock_response):
result = agent.run("What is the answer?", max_steps=5)
assert result.model_output == "Answer: 42"Mock judge responses for success/failure:
def test_judge_evaluation():
"""Test trajectory judgment"""
judge = TrajectoryJudge(config)
# Mock success response
with patch.object(judge, '_call_llm', return_value="SUCCESS"):
success = judge.judge_trajectory_success(query, trajectory, ...)
assert success is TrueMock extraction responses with Markdown format:
def test_memory_extraction():
"""Test memory extraction"""
extractor = MemoryExtractor(config)
# Mock extraction response (Markdown format)
mock_response = """
# Memory Item 1
## Title Strategy Title
## Description One sentence description
## Content Detailed content explaining the strategy in 1-5 sentences.
"""
with patch.object(extractor, '_call_llm', return_value=mock_response):
memories = extractor.extract_memories(...)
assert len(memories) == 1
assert memories[0].title == "Strategy Title"Use MockEnvironment for agent testing:
def test_agent_with_environment():
"""Test agent with mock environment"""
from tests.fixtures.mock_environments import create_arithmetic_environment
env = create_arithmetic_environment()
agent = ReasoningBankAgent(config, environment=env.execute_action)
result = agent.run("What is 25 * 4?", max_steps=10)
assert result.success is True- Overall Project: ≥90%
- Core Components: ≥95% (judge, extractor, retriever, consolidator)
- Agent Logic: ≥90%
- MaTTS Strategies: ≥85%
- Configuration: 100%
- Data Models: 100%
Generate coverage report:
pytest --cov=reasoningbank --cov-report=html --cov-report=term-missingView uncovered lines:
pytest --cov=reasoningbank --cov-report=term-missingCoverage by file:
pytest --cov=reasoningbank --cov-report=term__init__.pyfiles with only imports- Debug/logging code
- Exception handlers for external API errors (tested via mocking)
Follow this structure for new tests:
"""
ABOUTME: Tests for [component name]
ABOUTME: Tests [specific functionality]
"""
import pytest
from unittest.mock import Mock, patch
from reasoningbank import [components]
@pytest.fixture
def my_fixture():
"""Create fixture for testing"""
# Setup
resource = create_test_resource()
yield resource
# Teardown (if needed)
@pytest.mark.[category] # unit, integration, matts
class Test[ComponentName]:
"""Tests for [component] [aspect]"""
def test_[functionality]_[scenario](self, my_fixture):
"""Test [what] [when] [condition]"""
# Arrange
component = MyComponent(config)
# Act
result = component.do_something()
# Assert
assert result == expected- Test functions:
test_[what]_[scenario] - Test classes:
Test[ComponentName][Aspect] - Fixtures:
[resource]_fixtureor just[resource]
Examples:
test_judge_success_parsing()test_extractor_enforces_max_items()test_retriever_ranks_by_similarity()TestTrajectoryJudgeBasicTestMemoryExtractionWithSelfContrast
Every test must have a docstring explaining:
- What is being tested
- Expected behavior
- Paper reference (if validating paper specification)
Example:
def test_temperature_settings_match_paper(self):
"""
Test temperature settings match paper specifications (Appendix A.2).
Paper specifies:
- Agent: 0.7 (balanced exploration)
- Judge: 0.0 (deterministic judgments)
- Extractor: 1.0 (diverse extraction)
- Selector: 0.0 (consistent selection)
"""
config = ReasoningBankConfig(...)
assert config.agent_temperature == 0.7
assert config.judge_temperature == 0.0
assert config.extractor_temperature == 1.0
assert config.selector_temperature == 0.0Use descriptive assertion messages:
# Good
assert result.success is True, "Trajectory should be judged successful"
assert len(memories) == 3, f"Expected 3 memories, got {len(memories)}"
# Bad
assert result.success
assert len(memories) == 3Create .github/workflows/tests.yml:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest --cov=reasoningbank --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xmlCreate .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: pytest
name: pytest
entry: pytest
language: system
pass_filenames: false
always_run: trueInstall:
pip install pre-commit
pre-commit installProblem: ImportError: No module named 'reasoningbank'
Solution:
# Install package in development mode
pip install -e .Problem: fixture 'test_config' not found
Solution: Ensure conftest.py is in the correct location (tests/ directory)
Problem: Test artifacts remain after test run
Solution: Use tempfile.TemporaryDirectory() context manager:
def test_with_temp_dir():
with tempfile.TemporaryDirectory() as tmpdir:
# Test code using tmpdir
pass
# tmpdir automatically cleaned upProblem: Tests interfere with each other
Solution:
- Use fresh fixtures for each test
- Avoid global state
- Use
pytest -xto stop at first failure
Problem: Tests take too long to run
Solution:
- Run only changed tests:
pytest --lf(last failed) - Run in parallel:
pytest -n auto(requires pytest-xdist) - Skip slow tests:
pytest -m "not matts"
Run single test with output:
pytest tests/unit/test_judge.py::test_judge_success_parsing -s -vvDrop into debugger on failure:
pytest --pdbShow local variables on failure:
pytest -lCapture warnings:
pytest -W allAll tests validate against the ReasoningBank paper specifications:
- Title: Concise name
- Description: 1 sentence
- Content: 1-5 sentences
- Success signal: Boolean
Validated in: test_models.py, test_extractor.py
- Compares k trajectories
- Extracts robust patterns
- Max 5 aggregated items
Validated in: test_extractor.py, test_parallel.py
- Initial trajectory + k refinements
- Refinement prompts (Figure 10)
- Progressive improvement
Validated in: test_sequential.py
- Max 3 items per trajectory
- Max 5 aggregated items
Validated in: test_config.py, test_extractor.py
- Agent: 0.7
- Judge: 0.0
- Extractor: 1.0
- Selector: 0.0
Validated in: test_config.py, all component tests
- Markdown output format
- Dual prompts (success/failure)
Validated in: test_extractor.py
- Expert evaluation framework
- Three task types
- Success/failure determination
Validated in: test_judge.py
- Review previous attempt
- Try different strategy
- Execute best approach
Validated in: test_sequential.py
./tests/run_all_tests.py # All tests
pytest -m unit # Unit tests only
pytest -m integration # Integration tests only
pytest -m matts # MaTTS tests only
pytest --cov=reasoningbank # With coverage- Unit:
tests/unit/test_*.py(6 files, 200+ tests) - Integration:
tests/integration/test_*.py(2 files, 85 tests) - MaTTS:
tests/matts/test_*.py(2 files, 100+ tests)
@pytest.mark.unit: Unit test@pytest.mark.integration: Integration test@pytest.mark.matts: MaTTS scaling test
- Overall: ≥90%
- Core components: ≥95%
- Configuration: 100%
- ReasoningBank Paper: "ReasoningBank: Scaling Agent Self-Evolving with Reasoning Memory" (Google Cloud AI Research + UIUC, September 2025)
- Pytest Documentation: https://docs.pytest.org/
- Coverage.py: https://coverage.readthedocs.io/
- Project README:
README.md - API Documentation:
docs/API.md
For questions about the test suite:
- Create an issue in the repository
- Check existing test files for examples
- Review this documentation for guidelines
Last Updated: October 2025 Test Suite Version: 1.0.0