This document provides comprehensive information about the PaperAgent testing infrastructure.
The test suite is organized into three main categories:
tests/
├── conftest.py # Shared fixtures and configuration
├── pytest.ini # Pytest configuration
│
├── unit/ # Unit tests
│ ├── test_core.py # Core configuration and LLM tests
│ ├── test_agents.py # Agent functionality tests
│ ├── test_tools.py # Tool module tests
│ └── test_database.py # Database model tests
│
└── integration/ # Integration tests
├── test_workflows.py # End-to-end workflow tests
└── test_api.py # API endpoint tests
Unit tests focus on individual components in isolation:
- test_core.py: Tests for Settings, LLMManager, and Prompts
- test_agents.py: Tests for all agent classes (Boss, Literature, Experiment, Writing)
- test_tools.py: Tests for tools (ArxivCollector, LaTeXProcessor, AdvancedStatistics, etc.)
- test_database.py: Tests for database models and queries
Integration tests verify that components work together correctly:
- test_workflows.py: End-to-end research workflows
- test_api.py: FastAPI endpoint integration tests
# Run entire test suite
pytest
# Run with verbose output
pytest -v
# Run with coverage report
pytest --cov=paperagent --cov-report=html# Run only unit tests
pytest tests/unit/
# Run only integration tests
pytest tests/integration/
# Run specific test file
pytest tests/unit/test_core.py
# Run specific test class
pytest tests/unit/test_agents.py::TestLiteratureAgent
# Run specific test function
pytest tests/unit/test_agents.py::TestLiteratureAgent::test_search_literature# Run only unit tests (marked with @pytest.mark.unit)
pytest -m unit
# Run only integration tests
pytest -m integration
# Skip slow tests
pytest -m "not slow"
# Run slow tests only
pytest -m slow# Run tests in parallel using pytest-xdist
pytest -n auto # Uses all available CPUs
pytest -n 4 # Uses 4 CPUs# Generate HTML coverage report
pytest --cov=paperagent --cov-report=html
# Open report
# Windows
start htmlcov/index.html
# Linux/Mac
open htmlcov/index.htmlThe project enforces a minimum coverage threshold of 70%. This is configured in pytest.ini:
[pytest]
addopts = --cov-fail-under=70# Terminal coverage report
pytest --cov=paperagent --cov-report=termCommon fixtures are defined in conftest.py:
# Database fixtures
db_engine # SQLAlchemy engine (in-memory SQLite)
db_session # Database session
# Mock fixtures
mock_llm # Mock LLM manager
mock_arxiv_response
mock_scholar_response
# Sample data
sample_project_data
sample_literature_data
sample_dataframe
sample_codedef test_create_project(db_session, sample_project_data):
"""Test project creation"""
project = Project(**sample_project_data)
db_session.add(project)
db_session.commit()
assert project.id is not Nonefrom unittest.mock import patch
@patch('paperagent.tools.literature_collector.ArxivCollector')
def test_search_papers(mock_arxiv, mock_llm, db_session):
"""Test paper search with mocked API"""
mock_arxiv.return_value.search.return_value = [
{'title': 'Test Paper', 'authors': ['Author A']}
]
agent = LiteratureAgent(llm=mock_llm, db_session=db_session)
result = agent.execute({'action': 'search_literature', 'query': 'ML'})
assert 'total_papers' in resultimport pytest
@pytest.mark.asyncio
async def test_async_function():
"""Test async functionality"""
result = await some_async_function()
assert result is not Nonedef test_invalid_provider(mock_llm, db_session):
"""Test error handling for invalid provider"""
with pytest.raises(ValueError, match="Unsupported LLM provider"):
LLMManager(provider="invalid_provider")@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("test", "TEST")
])
def test_uppercase(input, expected):
assert input.upper() == expected- Test files:
test_*.py - Test functions:
test_* - Test classes:
Test*
class TestLiteratureAgent:
def test_search_literature(self):
pass
def test_analyze_gap(self):
passdef test_create_project(db_session):
# Arrange
project_data = {'name': 'Test', 'research_field': 'CS'}
# Act
project = Project(**project_data)
db_session.add(project)
db_session.commit()
# Assert
assert project.id is not None
assert project.name == 'Test'Always mock external API calls, file I/O, and database operations in unit tests:
@patch('paperagent.tools.literature_collector.requests.get')
def test_api_call(mock_get):
mock_get.return_value.json.return_value = {'data': 'test'}
# Test code here# Good
def test_literature_search_returns_correct_number_of_papers():
pass
# Bad
def test_search():
passEach test should be able to run independently and in any order.
def test_empty_input():
"""Test handling of empty input"""
pass
def test_null_input():
"""Test handling of null input"""
pass
def test_large_input():
"""Test handling of large dataset"""
passSome tests require specific conditions to run (API keys, databases, etc.):
# In conftest.py
skip_if_no_openai = pytest.mark.skipif(
not os.getenv('OPENAI_API_KEY'),
reason="OpenAI API key not available"
)
# In test file
@skip_if_no_openai
def test_openai_integration():
passCreate .github/workflows/tests.yml:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Run tests
run: |
pytest --cov=paperagent --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v2
with:
files: ./coverage.xml# Run with Python debugger
python -m pdb -m pytest tests/test_file.py
# Run with ipdb (install: pip install ipdb)
pytest --pdb # Drop into debugger on failure# Show print output even for passing tests
pytest -s
# Show output for failed tests only
pytest --tb=short# Very verbose
pytest -vv
# Show test durations
pytest --durations=10 # Show 10 slowest testspip install pytest-benchmarkdef test_performance(benchmark):
"""Test function performance"""
result = benchmark(expensive_function, arg1, arg2)
assert result is not NoneStore test data in tests/fixtures/:
# In conftest.py
@pytest.fixture
def sample_pdf():
return Path(__file__).parent / "fixtures" / "sample.pdf"def test_with_temp_file(tmp_path):
"""Test using temporary directory"""
temp_file = tmp_path / "test.txt"
temp_file.write_text("test content")
# Test code using temp_fileSolution: Tests may be sharing state. Check for:
- Database transactions not being rolled back
- Global variables being modified
- Mock objects not being reset
Solutions:
- Use
pytest-xdistfor parallel execution - Mark slow tests with
@pytest.mark.slow - Mock expensive operations
- Use in-memory databases for tests
Solutions:
- Avoid timing dependencies
- Mock random number generators
- Ensure proper test isolation
- Use
pytest-repeatto identify flaky tests:pytest --count=100 tests/test_flaky.py
For questions about testing:
- Check existing test examples in the codebase
- Refer to pytest documentation
- Open an issue on GitHub
Happy Testing! 🧪