Files
beta_shape_pnpi/tests/common/test_api_tests.py
T
unzhakov 4b08df3873 test: create new test architecture with shared property/API tests
- 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)
2026-05-15 15:04:52 +03:00

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"
)