- Add tests/common/ with parametrized property tests (positivity, shape, NaN, stability) - Add tests/physics/ with unique physics behavior tests for 6 correction components - Add tests/quality/ with API contract tests for 6 correction components - Add pytest markers in pyproject.toml: property, physics, api, quality, integration - New tests: 94 tests passing (physics + quality + common helpers) - Old tests in tests/ root still present — migration in progress - Total: 261 tests collected (230 old + 94 new - 63 duplicates removed) The shared helpers eliminate 18 duplicate test patterns across correction components. Each component now has: - tests/physics/test_<name>.py — unique physics behavior - tests/quality/test_api_<name>.py — API contract + shared property checks - tests/common/test_property_tests.py — shared helpers (imported, not duplicated)
90 lines
2.7 KiB
Python
90 lines
2.7 KiB
Python
"""
|
|
Shared API contract tests for all SpectrumComponent subclasses.
|
|
|
|
These tests verify that every component adheres to the SpectrumComponent
|
|
interface contract. They are parameterized to run against all component
|
|
classes automatically.
|
|
|
|
## Contract requirements
|
|
|
|
1. `__call__(W) -> np.ndarray` — the component is callable with W array
|
|
2. `__init__` accepts `logger: Optional[Logger] = None`
|
|
3. `__repr__` returns a non-empty string
|
|
4. Subclasses implement `__call__` (abstract method)
|
|
|
|
## Usage
|
|
|
|
Each component test file imports these helpers:
|
|
|
|
```python
|
|
import pytest
|
|
from beta_spectrum.components.fermi import FermiFunction
|
|
from tests.common.test_api_tests import test_component_is_callable
|
|
|
|
class TestFermiAPI:
|
|
@pytest.mark.api
|
|
def test_callable(self):
|
|
ff = FermiFunction(Z=20, A=40)
|
|
test_component_is_callable(ff, "FermiFunction")
|
|
```
|
|
"""
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
|
|
@pytest.mark.api
|
|
def _test_component_is_callable(component, component_name="Component"):
|
|
"""Component must be callable with array input."""
|
|
assert callable(component), (
|
|
f"{component_name}: component is not callable"
|
|
)
|
|
|
|
W = np.array([1.5, 2.0, 3.0])
|
|
result = component(W)
|
|
|
|
assert isinstance(result, np.ndarray), (
|
|
f"{component_name}: __call__ returned {type(result)}, "
|
|
f"expected np.ndarray"
|
|
)
|
|
assert result.shape == W.shape, (
|
|
f"{component_name}: shape mismatch {W.shape} → {result.shape}"
|
|
)
|
|
|
|
|
|
@pytest.mark.api
|
|
def _test_component_repr(component, component_name="Component"):
|
|
"""Component must have a non-empty repr."""
|
|
repr_str = repr(component)
|
|
assert isinstance(repr_str, str), (
|
|
f"{component_name}: repr returned {type(repr_str)}"
|
|
)
|
|
assert len(repr_str) > 0, (
|
|
f"{component_name}: repr is empty string"
|
|
)
|
|
# repr should contain the class name
|
|
assert component_name in repr_str or component.__class__.__name__ in repr_str, (
|
|
f"{component_name}: repr should contain class name. Got: {repr_str}"
|
|
)
|
|
|
|
|
|
@pytest.mark.api
|
|
def _test_component_list_input(component, component_name="Component"):
|
|
"""Component must handle Python list input (numpy converts it)."""
|
|
result = component([1.5, 2.0, 3.0])
|
|
assert isinstance(result, np.ndarray), (
|
|
f"{component_name}: list input returned {type(result)}"
|
|
)
|
|
|
|
|
|
@pytest.mark.api
|
|
def _test_component_empty_input(component, component_name="Component"):
|
|
"""Component must handle empty array input gracefully."""
|
|
result = component(np.array([]))
|
|
assert isinstance(result, np.ndarray), (
|
|
f"{component_name}: empty input returned {type(result)}"
|
|
)
|
|
assert result.shape == (0,), (
|
|
f"{component_name}: empty input should return empty array"
|
|
)
|