Skip to content

Test strategy (r1 — for review)

Comment on le valide. Stratégie de tests de S14 (ADR-0095 artefact 5/5). Le cœur est l'exhaustivité de la truth table Q1 (slice A, pure) — déjà implémentée et verte (12/12). Slice B (le fit in-pod) est testé par un wiring smoke + le dry-run in-pod (geste opérateur) ; un vrai fit local est impossible par design (ADR-90, §0bis du plan r4).

1. Objectifs & périmètre

Valider que la règle de décision pré-enregistrée Q1 (plan r4 §1) est implémentée exactement : chaque combinaison d'entrées → exactement un verdict + cause/reason, aucun fall-through, aucun crash, aucune valeur décisionnelle inventée hors plan. Hors périmètre : valider le verdict scientifique réel (ce n'est pas un test CI — §6) ; le vrai fit (in-pod, ADR-90).

2. Pyramide + tiers ADR-83

Tier Markers Quoi (S14)
fast (PR-blocking) unit slice A : decision core + probes + gardes (le fichier de test actuel, 12 tests)
medium dag_smoke (à venir) import + 1-step stub du DAG diagnostic__s14_q1
nightly ml_behaviour (post-S09) Q2 sur le replay blanchi
operator-driven post_deploy_smoke le dry-run in-pod de run_inpod (vrai fit, base_env injecté)

pytestmark = [pytest.mark.unit, pytest.mark.story("CVN-N001-EI-S14")] (présent dans le test slice-A).

3. Slice A — decision core (le cœur, implémenté)

tests/unit/finetune/diagnostic/test_s14_lgb_output_validity.py (12 tests, 12/12 vert) :

Test Couvre
test_synthetic_early_stopping_degenerate control positif #12 : 1 arbre / 156 → CONFIG_DEGENERATE_LGB(cause=early_stopping(eval_metric:aucpr)) (l'ablation localise)
test_synthetic_capacity_degenerate best_iter≈plafond + AUPRC≈hasard → cause=capacity/feature/label (pas early-stop)
test_synthetic_config_ok entraîne + rang OK → CONFIG_OK_LGB + note Q2/S09
test_best_iter_floor_with_healthy_rank_is_not_forced_underfit r2 #3 : best_iter≤floor n'override pas un rang sain si plafond≈best_iter (seul plafond≫best_iter = early-stop)
test_regime_base_rate_guard / test_regime_label_guard #4 : fold hors régime → INCONCLUSIVE_TOOLING(reason=regime_*)
test_leakage_guard #9 : fe_fitted_on_train_only=Falsereason=leakage
test_label_misalignment_guard / test_empty_preds_guard #10 : shape/align/empty → reason=labels/preds
test_power_gate_straddles_boundary #7 : IC de lift enjambe la frontière → INCONCLUSIVE_POWER (boundary-relative, pas un half-width absolu)
test_decision_is_total_and_single totalité/no-crash : tout input → exactement un verdict, as_event() valide, jamais de raise
test_metrics_sane skill > noise (auprc_lift), ECE ∈ [0,1]

Exhaustivité (le pendant de « jamais None ») : test_decision_is_total_and_single + les cas D1–D9 couvrent les classes d'équivalence de la truth table Q1 (plan r4 Fig 1). decide_q1 est pure → testable en isolation totale du data-prep (c'est l'intérêt du split A/B).

4. Slice B — fit in-pod (wiring + dry-run)

Slice B (s14_q1_fit_inpod.py) est I/O-couplé (cache, harness) → pas de test unitaire local d'un vrai fit (ADR-90 gate). Validé par : - wiring smoke (--smoke) : run_q1 + fit_fn sur un Datasets synthétique via le harness → no-crash + verdict structuré (preuve locale max). Vérifié. - dry-run in-pod (geste opérateur) : run_inpod dans un pod où base_env (ADR-90) est injecté → prouve le vrai fit + capture best_iteration + predict_proba sans crash. La preuve que le local ne peut pas faire.

5. Invariants

Inv Énoncé Type
I1 best_iteration réel mesuré (pas le plafond n_estimators) contract (slice B)
I2 rang = tail (AUPRC-lift), pas l'AUC global ; décidé sur l'IC over draws (median+max) property (slice A)
I3 ECE equal-mass (quantile), pas equal-width contract
I4 early-stop = structurel (best_iter≪plafond), best_iteration n'override pas un rang sain property
I5 régime-match (label + base_rate ±20 %) précondition dure contract
I6 no-crash : tout chemin → INCONCLUSIVE_TOOLING(reason), jamais un raise/print property
I7 valeurs décisionnelles figées dans le plan/module (pas inventées au run) contract

6. Hors-test (validation système, in-pod)

Le verdict réel Q1 (CONFIG_*) est une validation système in-pod (le dry-run §4 + le run décisionnel sur le fold de confiance), pas un test CI. Q2 (post-S09) sur le replay blanchi est nightly/operator.

7. Critères de done (gate PR)

  • make test-unit vert ; 12/12 slice A (truth-table exhaustivité + gardes + power).
  • wiring smoke --smoke no-crash.
  • black/isort(--profile black)/flake8 clean.
  • (au merge) DAG diagnostic__s14_q1 dag_smoke + ADR-92.
  • aucune valeur décisionnelle hors plan r4.