This document describes the testing strategy, infrastructure, and best practices for the TNFR Python Engine. It consolidates information about test organization, coverage requirements, and structural fidelity validation.
- Testing Philosophy
- Test Organization
- Running Tests
- Test Categories
- Structural Fidelity Tests
- Backend Selection
- Coverage Requirements
- Test Development Guidelines
TNFR tests validate structural coherence first, implementation details second. Every test must:
- Preserve TNFR Invariants: Verify canonical constraints (see ARCHITECTURE.md §3)
- Test Structural Behavior: Focus on coherence, phase, frequency, not implementation
- Maintain Reproducibility: Use seeds, validate determinism
- Guard Regressions: Performance, accuracy, and API stability
Tests are not responsible for:
- Fixing unrelated pre-existing failures
- Optimizing code that already passes
- Validating framework internals (NetworkX, NumPy)
The test suite is organized by concern and scope:
tests/
├── conftest.py # Shared fixtures and configuration
├── utils.py # Test utilities (module clearing, etc.)
├── unit/ # Unit tests for individual modules
│ ├── test_cache.py
│ ├── test_dynamics.py
│ ├── test_operators.py
│ ├── test_structural.py
│ └── ...
├── integration/ # Integration tests for subsystems
│ ├── test_glyph_sequences.py
│ ├── test_operator_chains.py
│ └── test_telemetry_pipeline.py
├── property/ # Property-based tests (Hypothesis)
│ └── test_tnfr_invariants.py
├── stress/ # Stress and scale tests
│ └── test_large_networks.py
├── performance/ # Performance regression tests
│ └── test_dnfr_pipeline.py
├── mathematics/ # Mathematical backend tests
│ └── test_epi.py
└── cli/ # CLI interface tests
└── test_cli.py
| File | Purpose | Markers |
|---|---|---|
test_extreme_cases.py |
Boundary value testing | unit |
test_glyph_sequences.py |
Grammar and sequence validation | integration |
test_tnfr_invariants.py |
Property-based invariant checks | property, slow |
test_dnfr_pipeline.py |
Performance regression guards | performance, slow |
test_trace.py |
Telemetry and debugging utilities | unit |
# Run all tests (excluding slow/benchmarks by default)
pytest
# Run specific test category
pytest tests/unit
pytest tests/integration
# Run with coverage report
pytest --cov=tnfr --cov-report=html
# Run verbose with output
pytest -v -sTelemetry dashboards are part of the reproducibility surface. A lightweight regression test (tests/test_precision_walk_dashboard_artifact.py) ensures benchmarks/results/precision_walk_dashboard.json is valid JSON (no NaN literals) and remains loadable by downstream tooling. It now runs automatically via:
# Curated smoke bundle (includes dashboard JSON guard)
make smoke-tests
# Run the guard directly when iterating on telemetry scripts
pytest tests/test_precision_walk_dashboard_artifact.pyTests are marked for selective execution:
# Run only fast tests (default in CI)
pytest -m "not slow"
# Run slow/property-based tests
pytest -m slow
# Run performance regression tests
pytest -m performance tests/performance
# Run backend-specific tests
pytest -m numpy_only
pytest -m requires_jaxDefault pytest options are configured in pyproject.toml:
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-m", "not slow", # Skip slow tests by default
"--benchmark-skip", # Skip benchmarks by default
"--strict-markers",
"--tb=short",
]
markers = [
"slow: marks tests as slow (deselected by default)",
"performance: marks performance regression tests",
"numpy_only: requires NumPy backend",
"requires_jax: requires JAX backend",
"requires_torch: requires PyTorch backend",
]Purpose: Test individual modules and functions in isolation.
Coverage Areas:
- Structural operators (emission, reception, coherence, etc.)
- Dynamics (ΔNFR computation, nodal equation integration)
- Cache layers (Shelve, Redis, Memory)
- Validation (sequence grammar, graph state)
- Telemetry (coherence, sense index, traces)
Example:
def test_coherence_operator_stabilizes_epi(simple_graph):
"""Coherence operator should increase stability."""
G, node = simple_graph
initial_coherence = compute_coherence(G)
apply_operator(G, node, "coherence")
final_coherence = compute_coherence(G)
assert final_coherence >= initial_coherencePurpose: Test interactions between subsystems and operator sequences.
Coverage Areas:
- Canonical glyph sequences (emission→reception→coherence)
- Operator chaining and graph state evolution
- Telemetry pipeline end-to-end
- Validation and execution coordination
Example:
def test_canonical_sequence_execution():
"""Test canonical TNFR sequence executes without errors."""
G, node = create_nfr("test", epi=1.0, vf=1.0)
sequence = ["emission", "reception", "coherence", "coupling"]
result = run_sequence(G, node, sequence)
assert result["success"]
assert compute_coherence(G) > 0Purpose: Use Hypothesis to generate test cases validating TNFR invariants.
Coverage Areas:
- Operator invariants (bounds preservation, no NaN/inf)
- Coherence monotonicity
- Phase coupling symmetry
- Frequency scaling laws
Example:
@given(
epi=st.floats(min_value=0.1, max_value=10.0),
vf=st.floats(min_value=0.1, max_value=10.0),
)
def test_coherence_operator_preserves_bounds(epi, vf):
"""Coherence operator should never produce NaN or infinite values."""
G, node = create_nfr("prop_test", epi=epi, vf=vf)
apply_operator(G, node, "coherence")
epi_after = G.nodes[node]["epi"]
assert np.isfinite(epi_after).all()Purpose: Validate behavior under extreme conditions and large scales.
Coverage Areas:
- Large networks (1000+ nodes)
- Long sequences (100+ operators)
- Memory usage patterns
- Parallel execution stability
Example:
@pytest.mark.slow
def test_large_network_coherence():
"""Test coherence computation on large networks."""
G = nx.DiGraph()
for i in range(1000):
G.add_node(i, epi=np.random.rand(10), vf=1.0, phase=0.0)
coherence = compute_coherence(G)
assert 0 <= coherence <= 1.0
assert np.isfinite(coherence)Purpose: Guard against performance regressions in critical paths.
Coverage Areas:
- ΔNFR computation pipeline
- Alias cache effectiveness
- Trigonometric metric calculations
- Sense index computation
Execution:
pytest -m performance tests/performanceThese tests validate the 6 TNFR canonical invariants. For complete invariant definitions and physics, see AGENTS.md § Canonical Invariants.
def test_nodal_equation_integrity():
"""EPI evolution must follow ∂EPI/∂t = νf · ΔNFR(t) only."""
G, node = create_nfr("test", epi=1.0, vf=1.0)
initial_epi = G.nodes[node]["epi"].copy()
# Changes occur only via structural operators
# Validates nodal equation constraint
apply_operator(G, node, "coherence")
assert not np.array_equal(G.nodes[node]["epi"], initial_epi)def test_structural_metrology_units():
"""Structural frequency must remain in Hz_str units with proper telemetry."""
G, node = create_nfr("test", epi=1.0, vf=2.5)
apply_operator(G, node, "mutation")
vf = G.nodes[node]["vf"]
assert isinstance(vf, (int, float))
assert vf > 0 # Positive Hz_strdef test_coupling_requires_phase_check():
"""Coupling should verify phase synchrony."""
G = nx.DiGraph()
n1 = G.add_node(1, epi=1.0, vf=1.0, phase=0.0)
n2 = G.add_node(2, epi=1.0, vf=1.0, phase=np.pi)
# Should validate phase compatibility
with pytest.raises(ValidationError):
apply_operator(G, n1, "coupling", target=n2)def test_deterministic_with_seed():
"""Same seed should produce same results."""
seed = 42
# Run 1
G1, node1 = create_nfr("test", epi=1.0, vf=1.0, seed=seed)
run_sequence(G1, node1, ["emission", "coherence"])
result1 = compute_coherence(G1)
# Run 2
G2, node2 = create_nfr("test", epi=1.0, vf=1.0, seed=seed)
run_sequence(G2, node2, ["emission", "coherence"])
result2 = compute_coherence(G2)
assert result1 == result2TNFR supports multiple mathematical backends (NumPy, JAX, PyTorch). Tests can specify backend requirements:
# Set backend before running tests
export TNFR_MATH_BACKEND=numpy # or jax, torch
pytest tests/mathematics# Use pytest option
pytest tests/mathematics --math-backend=torch@pytest.mark.requires_jax
def test_jax_specific_feature():
"""This test requires JAX backend."""
from tnfr.mathematics import get_backend
backend = get_backend()
assert backend.name == "jax"When a requested backend is unavailable, pytest automatically skips backend-specific tests while continuing with NumPy fallback.
- Overall: 80% line coverage
- Core modules (structural, dynamics, operators): 90%
- Telemetry (metrics, trace): 85%
- Utilities (cache, validation): 80%
- Nodal equation integration (
dynamics/integrators.py) - Operator registry and dispatch (
operators/registry.py) - Canonical sequence validation (
validation/__init__.py) - ΔNFR computation (
dynamics/dnfr.py)
# Generate HTML coverage report
pytest --cov=tnfr --cov-report=html
# Open report
open htmlcov/index.htmlUse clear_test_module() utility for test independence:
from tests.utils import clear_test_module
def test_fresh_import():
"""Test with fresh module state."""
clear_test_module('tnfr.utils.io')
import tnfr.utils.io # Fresh importNote: Module path checking ('module' in sys.modules) may trigger CodeQL warnings. This is legitimate test infrastructure, not URL validation. See ARCHITECTURE.md §"Test isolation and module management".
Prefer pytest fixtures for common setup:
@pytest.fixture
def simple_graph():
"""Create a simple test graph."""
G = nx.DiGraph()
node = G.add_node(1, epi=np.array([1.0, 2.0]), vf=1.0, phase=0.0)
return G, node
def test_with_fixture(simple_graph):
G, node = simple_graph
# Test implementationUse pytest.mark.parametrize for multiple test cases:
@pytest.mark.parametrize("operator", [
"emission", "reception", "coherence", "dissonance"
])
def test_operator_preserves_structure(operator):
"""All operators should preserve graph structure."""
G, node = create_nfr("test", epi=1.0, vf=1.0)
apply_operator(G, node, operator)
assert node in G.nodesEvery test should have a clear docstring explaining:
- What structural behavior is being validated
- Which TNFR invariant(s) are checked
- Why the test matters for coherence
def test_resonance_propagates_coherence():
"""
Resonance operator should propagate coherence to coupled nodes
without altering EPI identity (Invariant 1, 4).
This test validates that resonance maintains operational
fractality while increasing network coupling.
"""
# Test implementation- Don't assert exact floating-point equality (use
np.allclose) - Don't depend on internal implementation details
- Don't test framework internals (NetworkX, NumPy)
- Focus on structural semantics, not code paths
Mark slow tests appropriately:
@pytest.mark.slow
def test_expensive_computation():
"""This test takes >1 second to run."""
# Long-running testThe repository tracks known test failures that are not regressions:
- Import errors: 3 known (require infrastructure changes)
- Backend compatibility: Some tests may fail with specific backends
- Platform-specific: Windows vs Unix path handling
These are documented and should not block PR approval unless your changes introduce new failures.
All PRs must:
- Pass the default test suite (
pytest -m "not slow") - Maintain or improve coverage
- Not introduce new failures (beyond pre-existing)
- Pass all structural fidelity tests
When optimizing tests:
- Consolidate redundant tests across directories
- Use shared fixtures to reduce duplication
- Parametrize instead of copying test functions
- Profile slow tests and optimize data generation
# Show print statements and detailed assertions
pytest -v -s tests/unit/test_operators.py
# Show local variables on failure
pytest --showlocals# Run specific test function
pytest tests/unit/test_cache.py::test_shelve_layer_stores_data
# Run test by keyword match
pytest -k "coherence" tests/# Drop into debugger on failure
pytest --pdb
# Drop into debugger at test start
pytest --trace# Show warnings
pytest -W default
# Treat warnings as errors
pytest -W error- ARCHITECTURE.md: TNFR invariants and canonical constraints
- SECURITY.md: Security testing guidelines
- CONTRIBUTING.md: General contribution workflow
- tests/README.md: Detailed test suite organization
- pyproject.toml: Test configuration and markers
Last Updated: November 2025
Version: 0.0.3.3