Picarones / tests /integration /test_chantier5.py
Claude
test: rΓ©organiser les 110 fichiers tests/test_*.py par cercle architectural
d109222 unverified
Raw
History Blame
10.1 kB
"""Tests du chantier 5 (post-Sprint 97) β€” dΓ©coupage des monolithes.
Couvre :
- 5.A : :mod:`picarones.measurements.narrative.detectors` est dΓ©sormais un
package thΓ©matique de 6 sous-modules (1229 lignes β†’ 6 fichiers).
Tous les imports historiques restent accessibles.
- 5.B : :mod:`picarones.cli` est dΓ©sormais un package avec 6
sous-modules + ``__init__.py`` (1519 lignes β†’ 7 fichiers).
Le groupe ``cli`` reste exportΓ© pour l'entry-point ``pyproject.toml``.
"""
from __future__ import annotations
import pytest
# ──────────────────────────────────────────────────────────────────────────
# 5.A β€” narrative/detectors dΓ©composΓ© en 6 familles
# ──────────────────────────────────────────────────────────────────────────
class TestDetectorsPackage:
def test_detectors_is_now_a_package(self):
"""``detectors.py`` est devenu ``detectors/`` (package)."""
from picarones.measurements.narrative import detectors
# Un package a __path__, un module simple ne l'a pas
assert hasattr(detectors, "__path__"), (
"detectors devrait Γͺtre un package depuis le chantier 5"
)
@pytest.mark.parametrize("name", [
"detect_global_leader_cer",
"detect_statistical_tie",
"detect_significant_gap",
"detect_pareto_alternative",
"detect_stratum_winner",
"detect_stratum_collapse",
"detect_error_profile_outlier",
"detect_llm_hallucination_flag",
"detect_robustness_fragile",
"detect_cost_outlier",
"detect_speed_winner",
"detect_confidence_warning",
"detect_median_mean_gap_warning",
"detect_stratification_recommended",
"detect_engine_off_baseline",
"detect_engine_unstable",
"detect_regression_in_history",
"detect_ensemble_opportunity",
])
def test_all_18_detectors_importable_from_root(self, name):
"""RΓ©trocompat : les 18 dΓ©tecteurs s'importent depuis le package
comme avant le chantier 5 (tests Sprints 20, 23, 29, 36, 44, 46, 73)."""
from picarones.measurements.narrative import detectors
assert hasattr(detectors, name), f"{name} disparu après chantier 5"
assert callable(getattr(detectors, name))
def test_DETECTORS_BY_TYPE_still_exposed(self):
from picarones.measurements.narrative.detectors import DETECTORS_BY_TYPE
assert isinstance(DETECTORS_BY_TYPE, dict)
assert len(DETECTORS_BY_TYPE) == 18, (
f"DETECTORS_BY_TYPE doit contenir 18 entrΓ©es, en a {len(DETECTORS_BY_TYPE)}"
)
def test_register_default_detectors_still_callable(self):
from picarones.measurements.narrative.detectors import register_default_detectors
assert callable(register_default_detectors)
@pytest.mark.parametrize("submodule, detector_count", [
("ranking", 5),
("pareto", 2),
("stratum", 3),
("quality", 4),
("history", 3),
("ensemble", 1),
])
def test_submodules_have_expected_detector_count(self, submodule, detector_count):
"""Chaque sous-module thΓ©matique a le bon nombre de dΓ©tecteurs."""
import importlib
mod = importlib.import_module(
f"picarones.measurements.narrative.detectors.{submodule}"
)
detectors_in_sub = [
n for n in dir(mod)
if n.startswith("detect_") and callable(getattr(mod, n))
]
assert len(detectors_in_sub) == detector_count, (
f"{submodule} : {len(detectors_in_sub)} dΓ©tecteurs trouvΓ©s, "
f"{detector_count} attendus β€” {detectors_in_sub}"
)
def test_identity_through_submodule_and_root(self):
"""Le dΓ©tecteur exposΓ© depuis __init__.py et depuis son sous-module
est la mΓͺme fonction (pas de redΓ©finition)."""
from picarones.measurements.narrative.detectors import detect_global_leader_cer
from picarones.measurements.narrative.detectors.ranking import (
detect_global_leader_cer as via_submodule,
)
assert detect_global_leader_cer is via_submodule
def test_detector_smoke_via_root(self):
"""Smoke test : un dΓ©tecteur fonctionne via l'import root."""
from picarones.measurements.narrative.detectors import detect_global_leader_cer
result = detect_global_leader_cer({
"ranking": [
{"engine": "tess", "mean_cer": 0.05},
{"engine": "pero", "mean_cer": 0.07},
],
})
assert len(result) == 1
assert result[0].payload["engine"] == "tess"
def test_helpers_are_in_dedicated_module(self):
"""Les helpers internes (_engines_summary, etc.) vivent dans
``_helpers.py`` (pattern modulaire propre)."""
from picarones.measurements.narrative.detectors import _helpers
assert hasattr(_helpers, "_engines_summary")
assert hasattr(_helpers, "_engine_by_name")
assert hasattr(_helpers, "_n_docs")
# ──────────────────────────────────────────────────────────────────────────
# 5.B β€” cli.py dΓ©composΓ© en package
# ──────────────────────────────────────────────────────────────────────────
class TestCliPackage:
def test_cli_is_now_a_package(self):
try:
import picarones.cli as cli_pkg
except ImportError as exc:
if "click" in str(exc):
pytest.skip("click non installΓ©")
raise
assert hasattr(cli_pkg, "__path__"), (
"picarones.cli devrait Γͺtre un package depuis le chantier 5"
)
def test_cli_group_still_exported(self):
"""L'entry-point ``picarones.cli:cli`` (pyproject.toml) doit
rester valide après le chantier 5."""
try:
from picarones.cli import cli
except ImportError as exc:
if "click" in str(exc):
pytest.skip("click non installΓ©")
raise
assert cli is not None
def test_helpers_still_exported(self):
"""``_setup_logging`` et ``_engine_from_name`` restent accessibles
depuis ``picarones.cli`` (les sous-modules les utilisent)."""
try:
import picarones.cli as cli_pkg
except ImportError as exc:
if "click" in str(exc):
pytest.skip("click non installΓ©")
raise
assert callable(cli_pkg._setup_logging)
assert callable(cli_pkg._engine_from_name)
@pytest.mark.parametrize("submodule", [
"_workflows",
"_imports",
"_serve",
"_history",
"_robustness",
"_pipeline",
])
def test_submodule_loaded(self, submodule):
try:
import picarones.cli as cli_pkg
except ImportError as exc:
if "click" in str(exc):
pytest.skip("click non installΓ©")
raise
assert hasattr(cli_pkg, submodule), (
f"{submodule} non chargΓ© en cascade β€” les commandes de cette "
"famille ne seraient pas enregistrΓ©es"
)
@pytest.mark.parametrize("cmd_name", [
"run", "diagnose", "economics", "edition", "compare",
"metrics", "engines", "info", "report", "demo",
"serve", "history", "robustness", "pipeline", "import",
])
def test_all_15_commands_registered(self, cmd_name):
"""Les 15 commandes/groupes historiques doivent Γͺtre enregistrΓ©s
sur le groupe ``cli`` après l'import en cascade."""
try:
from picarones.cli import cli
except ImportError as exc:
if "click" in str(exc):
pytest.skip("click non installΓ©")
raise
assert hasattr(cli, "commands"), (
"le groupe cli devrait avoir un attribut commands (Click Group)"
)
assert cmd_name in cli.commands, (
f"commande '{cmd_name}' manquante après le chantier 5 — "
f"commandes prΓ©sentes : {sorted(cli.commands.keys())}"
)
# ──────────────────────────────────────────────────────────────────────────
# 5.C β€” runner.py reste tel quel (dΓ©jΓ  allΓ©gΓ© au chantier 2)
# ──────────────────────────────────────────────────────────────────────────
class TestRunnerStillReachable:
"""Le chantier 2 a dΓ©jΓ  allΓ©gΓ© ``runner.py`` de 303 lignes (1322 β†’ 1019).
Le chantier 5 ne le dΓ©coupe pas davantage β€” vΓ©rification que les
fonctions historiques restent accessibles."""
@pytest.mark.parametrize("name", [
"run_benchmark",
"_compute_document_result",
"_cpu_doc_worker",
"_io_doc_worker",
"_aggregate_confusion",
"_aggregate_calibration",
"_calibration_from_engine_result",
"_aggregate_ner",
"_attach_ner_metrics",
])
def test_function_still_in_runner(self, name):
try:
from picarones.measurements import runner
except ImportError as exc:
if "tqdm" in str(exc):
pytest.skip("tqdm non installΓ©")
raise
assert hasattr(runner, name), (
f"runner.{name} a disparu"
)
assert callable(getattr(runner, name))