Testing¶
py2max has comprehensive test coverage (~97%) ensuring reliability and correctness. This guide covers the testing framework and practices.
Test Overview¶
Test Framework¶
py2max uses pytest as the primary testing framework with additional tools:
- pytest: Test runner and framework
- pytest-cov: Coverage measurement
- mypy: Static type checking
- ruff: Code quality and linting
Test Statistics¶
Current test metrics:
- 167 tests passing, 13 skipped
- 97% code coverage
- 60 test files covering all modules
- Zero mypy errors
- Zero ruff violations
Running Tests¶
Basic Test Execution¶
# Run all tests
uv run pytest
# Run with verbose output
uv run pytest -v
# Run specific test file
uv run pytest tests/test_core.py
# Run specific test function
uv run pytest tests/test_core.py::test_patcher_creation
# Run tests matching pattern
uv run pytest -k "layout"
Coverage Reports¶
# Generate coverage report
uv run pytest --cov=py2max --cov-report=html
# View coverage in terminal
uv run pytest --cov=py2max --cov-report=term
# Generate detailed coverage
make coverage # Equivalent to above with HTML output
The HTML coverage report is generated in outputs/_covhtml/index.html.
Test Categories¶
Core Functionality Tests¶
Location: tests/test_core*.py, tests/test_basic.py
Tests core classes and functionality:
Coverage includes:
- Patcher creation and manipulation
- Box object creation and properties
- Patchline connections and validation
- File I/O operations
- Error handling
Layout Manager Tests¶
Location: tests/test_layout*.py
Tests all layout algorithms and positioning:
# Run layout tests
uv run pytest tests/test_layout* -v
# Run specific layout tests
uv run pytest tests/test_layout_builtins.py -v
uv run pytest tests/test_layout_flow.py -v
Coverage includes:
- Grid layout with clustering
- Flow layout with signal analysis
- Legacy horizontal/vertical layouts
- Layout optimization algorithms
- Positioning calculations
MaxRef Integration Tests¶
Location: tests/test_maxref.py
Tests Max object discovery and documentation:
Coverage includes:
- Object discovery from .maxref.xml files
- Help text generation
- Connection validation
- Inlet/outlet counting
- Legacy compatibility
Connection Validation Tests¶
Location: tests/test_connection_validation.py
Tests connection validation system:
Coverage includes:
- Valid connection acceptance
- Invalid connection rejection
- Error message generation
- Object introspection methods
- Validation configuration
Abstract Base Class Tests¶
Location: tests/test_abstract_coverage.py
Tests abstract interfaces and type safety:
Coverage includes:
- Abstract method enforcement
- Interface compliance
- Type checking integration
- Circular dependency prevention
Object-Specific Tests¶
Location: tests/test_*.py (individual object types)
Tests specific Max object types:
# Run object-specific tests
uv run pytest tests/test_coll.py -v
uv run pytest tests/test_table.py -v
uv run pytest tests/test_message.py -v
Coverage includes:
- Specialized object creation methods
- Object-specific parameters
- Data container functionality
- UI element behavior
Test Data and Fixtures¶
Test Fixtures¶
Common fixtures used across tests:
import pytest
from py2max import Patcher
@pytest.fixture
def basic_patcher():
"""Create a basic patcher for testing."""
return Patcher('test.maxpat')
@pytest.fixture
def connected_objects():
"""Create patcher with connected objects."""
p = Patcher('connected.maxpat')
osc = p.add_textbox('cycle~ 440')
gain = p.add_textbox('gain~')
p.add_line(osc, gain)
return p, osc, gain
Test Data Generation¶
Tests use various data generation strategies:
# Parametrized tests for multiple scenarios
@pytest.mark.parametrize("layout_type", ["grid", "flow", "horizontal"])
def test_layout_managers(layout_type):
p = Patcher('test.maxpat', layout=layout_type)
# Test implementation
# Property-based testing for edge cases
def test_rect_operations():
for x, y, w, h in [(0, 0, 10, 10), (100, 200, 50, 75)]:
rect = Rect(x, y, w, h)
assert rect.x == x
Output Validation¶
Tests validate generated .maxpat files:
def test_patch_json_structure():
p = Patcher('test.maxpat')
osc = p.add_textbox('cycle~ 440')
# Generate JSON and validate structure
patch_dict = p.to_dict()
assert 'boxes' in patch_dict
assert len(patch_dict['boxes']) == 1
# Validate Max-compatible format
box_data = patch_dict['boxes'][0]['box']
assert box_data['maxclass'] == 'newobj'
assert 'cycle~ 440' in box_data['text']
Performance Testing¶
Layout Performance Tests¶
Some tests measure layout algorithm performance:
These tests verify that:
- Layout algorithms scale with object count
- Clustering performance is acceptable for large patches
- Memory usage remains reasonable
Optional Dependency Tests¶
Tests that require optional dependencies are skipped if not available:
# These will be skipped if networkx not installed
uv run pytest tests/test_layout_networkx* -v
# Install optional deps to run all tests
pip install networkx pygraphviz
Writing Tests¶
Test Structure¶
Follow this structure for new tests:
"""Tests for new feature functionality."""
import pytest
from py2max import Patcher, Box, Patchline
from py2max.common import Rect
class TestNewFeature:
"""Test new feature functionality."""
def test_basic_functionality(self):
"""Test basic feature operation."""
p = Patcher('test.maxpat')
# Test implementation
assert expected_result
def test_error_conditions(self):
"""Test error handling."""
p = Patcher('test.maxpat')
with pytest.raises(ExpectedError):
# Code that should raise error
pass
@pytest.mark.parametrize("input_value,expected", [
(1, "result1"),
(2, "result2"),
])
def test_parametrized_behavior(self, input_value, expected):
"""Test with multiple parameter sets."""
result = function_under_test(input_value)
assert result == expected
Testing Guidelines¶
When writing tests:
- Test public APIs - Focus on user-facing functionality
- Include edge cases - Test boundary conditions
- Test error conditions - Verify proper error handling
- Use descriptive names - Test names should explain what they test
- Keep tests independent - Each test should work in isolation
- Mock external dependencies - Don't rely on Max installation
- Validate outputs - Check generated .maxpat files are correct
Example Test Implementation¶
def test_connection_validation():
"""Test that connection validation works correctly."""
p = Patcher('test.maxpat', validate_connections=True)
# Create objects
osc = p.add_textbox('cycle~ 440')
gain = p.add_textbox('gain~')
# Valid connection should work
line = p.add_line(osc, gain)
assert line.src == osc.id
assert line.dst == gain.id
# Invalid connection should raise error
with pytest.raises(InvalidConnectionError) as exc_info:
p.add_line(osc, gain, outlet=10) # cycle~ has only 1 outlet
assert "outlet 10" in str(exc_info.value)
assert "cycle~" in str(exc_info.value)
def test_layout_clustering():
"""Test that grid layout clustering works."""
p = Patcher('test.maxpat', layout="grid", cluster_connected=True)
# Create connected objects
objects = []
for i in range(6):
obj = p.add_textbox(f'object{i}')
objects.append(obj)
# Connect in two separate chains
p.add_line(objects[0], objects[1])
p.add_line(objects[1], objects[2])
p.add_line(objects[3], objects[4])
p.add_line(objects[4], objects[5])
# Get initial positions
initial_positions = [obj.patching_rect for obj in objects]
# Optimize layout
p.optimize_layout()
# Get final positions
final_positions = [obj.patching_rect for obj in objects]
# Verify clustering worked (positions changed)
position_changes = sum(1 for i, f in zip(initial_positions, final_positions) if i != f)
assert position_changes >= len(objects) // 2 # At least half moved
Continuous Integration¶
Automated Testing¶
The project uses CI to run tests automatically:
- On every push to main branch
- On every pull request
- Multiple Python versions (when configured)
- Multiple operating systems (when configured)
CI Pipeline includes:
- Install dependencies
- Run full test suite
- Check code coverage
- Run type checking (mypy)
- Run linting (ruff)
- Build documentation
Local CI Simulation¶
Simulate CI locally before pushing:
# Run complete test suite like CI
uv run pytest --cov=py2max
uv run mypy py2max
uv run ruff check py2max
# Build docs like CI
cd docs
uv run mkdocs build --strict
Test Maintenance¶
Keeping Tests Current¶
- Update tests when changing functionality
- Add tests for bug fixes to prevent regression
- Remove obsolete tests when features are removed
- Refactor tests to maintain clarity
Test Performance¶
- Keep test suite fast - aim for \<5 seconds total runtime
- Skip expensive tests in development (mark with @pytest.mark.slow)
- Use mocking for external dependencies
- Parallelize tests when possible
Coverage Goals¶
Maintain high coverage while focusing on:
- Critical paths - Core functionality must be 100% covered
- Error conditions - All error paths should be tested
- Public APIs - All user-facing code must have tests
- Edge cases - Boundary conditions and unusual inputs
The test suite is a critical part of py2max's reliability and should be maintained with the same care as the production code.