Skip to content

Testing Quick Reference

For developers working on Complex Network RAG


Running Tests

# Run all tests
pytest

# Run all tests with coverage
pytest --cov=src --cov-report=term-missing

# Run specific test file
pytest tests/test_repl_session_state.py -v

# Run tests matching pattern
pytest -k "session" -v

# Run with verbose output
pytest -v

# Run fast tests only (skip slow)
pytest -m "not slow"

# Run with parallel execution
pytest -n auto

Test Organization

tests/
├── test_storage.py                    # Storage layer (100% coverage)
├── test_embeddings.py                 # Embedding providers (82%)
├── test_network_rag.py                # Core RAG class (91%)
├── test_fluent_api.py                 # Fluent API (98%)
├── test_cli.py                        # CLI interface (99%)
├── test_repl_basic.py                 # REPL basics (100%)
├── test_repl_commands.py              # REPL commands (87%)
├── test_repl_config.py                # REPL config (92%)
├── test_repl_session_state.py         # REPL sessions (NEW)
├── test_config_builder.py             # Config templates (100%)
├── test_yaml_parser.py                # YAML DSL (82%)
├── test_structured_integration.py     # Integration tests (99%)
└── test_hybrid_linkage_integration.py # Integration tests (100%)

Coverage Targets by Module

Priority Module Current Target Status
Storage 100% 100% Excellent
Linkage 100% 100% Excellent
Fluent API 98% 95%+ Excellent
NetworkRAG 91% 90%+ Good
⚠️ REPL 70% 90% Needs work
Config Builder 30% 85% CRITICAL
LLM Providers 43% 85% HIGH
Visualization 8% 60% MEDIUM

Writing Good Tests: Quick Checklist

✅ DO

  • Test behavior, not implementation

    # Good: Tests what the system does
    assert session.connected == True
    

  • Use Given-When-Then structure

    # Given: Setup state
    session = ReplSession()
    
    # When: Perform action
    session.connect(db_path)
    
    # Then: Verify outcome
    assert session.connected
    

  • Write descriptive test names

    def test_session_remains_usable_after_connection_error(self):
        """Clear description of what's being tested"""
    

  • Test error paths

    with pytest.raises(ValueError, match="No embeddings found"):
        rag.build_network()
    

  • Use fixtures for common setup

    @pytest.fixture
    def memory_storage():
        return SQLiteStorage(":memory:")
    

❌ DON'T

  • Test private methods

    # Bad: Testing internal implementation
    assert rag._internal_cache is None
    

  • Write brittle assertions

    # Bad: Exact string matching
    assert str(error) == "Error: Node not found in database"
    
    # Good: Partial match
    assert "Node not found" in str(error)
    

  • Create test dependencies

    # Bad: Tests depend on execution order
    def test_step1():
        global shared_state
        shared_state = setup()
    
    def test_step2():
        # Depends on test_step1
        use(shared_state)
    

  • Mock everything

    # Bad: Over-mocking hides integration issues
    with patch('storage'), patch('embedder'), patch('graph'):
        # Not testing anything real
    


Common Test Patterns

Pattern 1: Testing Properties

def test_connected_property_reflects_storage_state(self):
    """Test connected property returns True when storage exists"""
    session = ReplSession()

    # Initially not connected
    assert not session.connected

    # After connecting
    session.storage = SQLiteStorage(":memory:")
    assert session.connected

Pattern 2: Testing Lifecycle

def test_complete_session_lifecycle(self):
    """Test session through connect → use → disconnect"""
    session = ReplSession()

    # Connect
    session.connect(":memory:")
    assert session.connected

    # Use
    session.add_document("content")
    assert session.storage.count() == 1

    # Disconnect
    session.disconnect()
    assert not session.connected

Pattern 3: Testing Error Recovery

def test_system_survives_operation_error(self):
    """Test system remains usable after error"""
    system = System()

    # Cause error
    with pytest.raises(ValueError):
        system.invalid_operation()

    # System still works
    system.valid_operation()  # Should succeed
    assert system.is_operational()

Pattern 4: Testing Integration

def test_end_to_end_workflow(self):
    """Test complete user workflow"""
    # Setup
    rag = setup_rag()

    # Add documents
    rag.add_node("doc1", "content1")
    rag.add_node("doc2", "content2")

    # Build network
    rag.build_network()

    # Search
    results = rag.search("content").top(5)

    # Verify
    assert len(results) > 0

Debugging Failing Tests

1. Run with verbose output

pytest tests/test_file.py::test_name -v

2. Show print statements

pytest tests/test_file.py::test_name -v -s

3. Drop into debugger on failure

pytest tests/test_file.py::test_name --pdb

4. Show full traceback

pytest tests/test_file.py::test_name -v --tb=long

5. Run only failed tests

pytest --lf

Fixtures Reference

Common Fixtures Available

# In tests/conftest.py (if it exists) or individual test files

@pytest.fixture
def temp_db():
    """Temporary database file"""
    # Use in tests that need file-based storage

@pytest.fixture
def memory_storage():
    """In-memory SQLite storage"""
    # Use for fast tests

@pytest.fixture
def tfidf_embedder():
    """TF-IDF embedding provider"""
    # Use for tests that need embeddings

@pytest.fixture
def basic_rag(memory_storage, tfidf_embedder):
    """Basic NetworkRAG instance"""
    # Use for tests that need RAG

@pytest.fixture
def populated_rag(basic_rag):
    """RAG with sample documents"""
    # Use for tests that need data

Coverage Analysis

Generate HTML coverage report

pytest --cov=src --cov-report=html
open htmlcov/index.html  # Or xdg-open on Linux

Show missing lines

pytest --cov=src --cov-report=term-missing

Coverage for specific module

pytest --cov=src.repl --cov-report=term-missing

Fail if coverage drops

pytest --cov=src --cov-fail-under=90

Test Markers (for organization)

Define markers in pytest.ini

[pytest]
markers =
    slow: marks tests as slow (deselect with '-m "not slow"')
    integration: marks tests as integration tests
    repl: marks REPL-specific tests
    cli: marks CLI-specific tests

Use markers in tests

@pytest.mark.slow
def test_large_network_build():
    """Test with 10k+ nodes"""
    # Slow test

@pytest.mark.integration
def test_complete_workflow():
    """End-to-end integration test"""
    # Integration test

Run tests by marker

# Skip slow tests
pytest -m "not slow"

# Run only integration tests
pytest -m integration

# Run REPL tests
pytest -m repl

Common Assertions

Basic assertions

assert condition
assert value == expected
assert value != unexpected
assert value in collection
assert value is None
assert value is not None

Approximate equality (for floats)

assert abs(value - expected) < 0.01
# Or use pytest.approx
assert value == pytest.approx(expected, abs=0.01)

Exception testing

# Basic exception
with pytest.raises(ValueError):
    function_that_raises()

# Exception with message check
with pytest.raises(ValueError, match="specific message"):
    function_that_raises()

# Capture exception for inspection
with pytest.raises(ValueError) as exc_info:
    function_that_raises()
assert "expected text" in str(exc_info.value)

Collection assertions

assert len(collection) == expected_length
assert item in collection
assert set(collection) == expected_set
assert collection[0] == first_item

Mocking (Use Sparingly)

Mock a function

from unittest.mock import Mock, patch

def test_with_mocked_function():
    with patch('module.function') as mock_func:
        mock_func.return_value = "mocked result"
        result = code_that_calls_function()
        assert result == "mocked result"
        mock_func.assert_called_once()

Mock a class

def test_with_mocked_class():
    mock_obj = Mock()
    mock_obj.method.return_value = "result"

    # Use mock_obj in test
    assert mock_obj.method() == "result"

When to mock

  • External services (APIs, databases in some cases)
  • Slow operations (when isolating unit logic)
  • Non-deterministic behavior (time, random)

When NOT to mock

  • Internal application logic (test real behavior)
  • Simple functions (just call them)
  • Storage operations (use test databases)

Test Data Management

Use builders for complex setup

class DocumentBuilder:
    def __init__(self):
        self.data = {}

    def with_title(self, title):
        self.data['title'] = title
        return self

    def with_content(self, content):
        self.data['content'] = content
        return self

    def build(self):
        return self.data

# Usage
doc = (DocumentBuilder()
       .with_title("Test")
       .with_content("Content")
       .build())

Use fixtures for shared data

@pytest.fixture
def sample_documents():
    return [
        {"id": "doc1", "content": "Content 1"},
        {"id": "doc2", "content": "Content 2"},
    ]

def test_something(sample_documents):
    # Use sample_documents
    pass

Performance Testing

Time test execution

pytest --durations=10  # Show 10 slowest tests

Profile tests

pytest --profile

Mark slow tests

@pytest.mark.slow
def test_large_operation():
    """Test that takes >1 second"""
    pass

CI/CD Integration

GitHub Actions example

name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-cov

      - name: Run tests
        run: pytest --cov=src --cov-report=xml

      - name: Upload coverage
        uses: codecov/codecov-action@v3

Quick Tips

  1. Run tests often - After every significant change
  2. Write tests first - TDD when possible
  3. Keep tests fast - Under 10ms for unit tests
  4. One assertion per test - Logical assertion (can be multiple assert statements)
  5. Test edge cases - Empty lists, None values, boundaries
  6. Clean up resources - Use fixtures with yield
  7. Read test output - Understand why tests fail
  8. Update tests when requirements change - Keep tests current
  9. Don't skip tests - Fix or remove them
  10. Review coverage regularly - Know what's not tested

Getting Help

Documentation

  • pytest --help - All pytest options
  • pytest --markers - List all markers
  • pytest --fixtures - List all fixtures

Analysis Files

  • TEST_SUITE_ANALYSIS.md - Comprehensive analysis
  • TESTING_IMPROVEMENTS_SUMMARY.md - What was done
  • This file - Quick reference

Coverage Reports

  • Run: pytest --cov=src --cov-report=html
  • View: htmlcov/index.html

Remember: Good tests enable fearless refactoring and confident deployment.

Test early. Test often. Test well.