Testing Guide¶
Fennel has a comprehensive test suite covering API functionality, validation, physics calculations, and backward compatibility.
Quick Start¶
# Run all tests
pytest
# Fast tests only (skip slow tests)
pytest -m "not slow"
# With verbose output
pytest -v
# With coverage report
pytest --cov=fennel --cov-report=html
Test Structure¶
tests/
├── conftest.py # Shared fixtures
├── test_config.py # Configuration tests
├── test_em_cascades.py # EM cascade tests
├── test_hadron_cascades.py # Hadron cascade tests
├── test_tracks.py # Track yield tests
├── test_v2_api.py # v2 API tests
├── test_integration.py # Integration tests
└── test_physics_regression.py # Physics validation
Test Categories¶
Unit Tests¶
Test individual components in isolation:
Integration Tests¶
Test component interactions:
Physics Regression Tests¶
Validate physics calculations against reference values:
These tests ensure: - Photon yields match expected values - Longitudinal profiles are correct - Angular distributions are accurate
v2 API Tests¶
Test new v2 API features:
Coverage includes:
- Result container classes
- Convenience methods (quick_track, quick_cascade, calculate)
- Input validation
- Backward compatibility
Markers¶
Tests are marked for selective execution:
@pytest.mark.unit- Unit tests@pytest.mark.integration- Integration tests@pytest.mark.physics- Physics validation@pytest.mark.slow- Slow tests (skip in fast mode)@pytest.mark.jax- JAX-specific tests (requires JAX)
Example:
@pytest.mark.physics
@pytest.mark.slow
def test_detailed_physics():
# Long-running physics test
pass
Using Make Commands¶
The Makefile provides convenient test commands:
make test # All tests
make test-fast # Skip slow tests
make test-unit # Unit tests only
make test-integration # Integration tests only
make test-physics # Physics tests only
make test-cov # Tests with coverage report
Writing Tests¶
Test Fixtures¶
Use pytest fixtures for common setup:
import pytest
from fennel import Fennel
@pytest.fixture
def fennel_instance():
f = Fennel()
yield f
f.close()
def test_something(fennel_instance):
result = fennel_instance.quick_track(energy=100.0)
assert result.energy == 100.0
Test Structure¶
Follow AAA pattern (Arrange, Act, Assert):
def test_track_yields_v2():
# Arrange
f = Fennel()
energy = 100.0
# Act
result = f.track_yields_v2(energy=energy, particle=13)
# Assert
assert result.energy == energy
assert result.particle_name == "μ⁻"
assert result.photons.shape[0] > 0
f.close()
Testing Validation¶
Test that validation catches bad inputs:
import pytest
from fennel import Fennel, ValidationError
def test_negative_energy_raises():
f = Fennel()
with pytest.raises(ValidationError, match="Energy must be positive"):
f.quick_track(energy=-100.0)
f.close()
Testing Edge Cases¶
Always test boundary conditions:
def test_zero_energy():
# Test behavior at boundaries
pass
def test_extreme_energy():
# Test at extreme values
pass
def test_invalid_particle():
# Test error handling
pass
Coverage Requirements¶
- Aim for >80% coverage on new code
- Critical paths should have 100% coverage
- Test both success and failure cases
View coverage report:
Continuous Integration¶
GitHub Actions runs tests automatically on: - Every push - Every pull request - Multiple Python versions (3.8, 3.9, 3.10, 3.11) - Multiple platforms (Ubuntu, macOS, Windows)
Reference Values¶
Physics tests compare against reference values in:
- tests/reference_values_v1.3.4.json
To regenerate reference values:
⚠️ Only regenerate if you've intentionally changed physics calculations!
Debugging Tests¶
Run specific test¶
Show print statements¶
Drop into debugger on failure¶
Verbose output¶
Performance Testing¶
For performance-critical code:
Best Practices¶
- ✅ Test one thing per test function
- ✅ Use descriptive test names
- ✅ Keep tests independent
- ✅ Use fixtures for setup
- ✅ Test edge cases
- ✅ Test error handling
- ✅ Keep tests fast (mark slow tests)
- ✅ Don't test implementation details
See also: PR Guide | Contributing