Audit de régression / hygiène des tests

VérifiéPrudence

Audite la santé des suites de tests : lacunes de couverture, assertions faibles, tests flaky/obsolètes. Fournit une liste priorisée de tests à ajouter ou renforcer pour prévenir les régressions.

Spar Skills Guide Bot
TestingIntermédiaire
07/08/2026
Claude Code
#test-coverage#regression-testing#pytest#test-audit#flaky-tests

Recommandé pour

Synthèse automatique

Audite la santé de la suite de tests d'un projet : lacunes de couverture, assertions trop faibles, tests obsolètes ou instables, pour produire une liste priorisée de tests à ajouter ou renforcer.

Points forts

  • Pondère les lacunes de couverture par le risque réel (chemin des registres NES, exportateurs, mappers, compilateur) au lieu d'un simple pourcentage global.
  • Prévoit de revérifier à chaque exécution l'état des régressions déjà corrigées (REG-01 à REG-11) plutôt que de les signaler de nouveau.
  • Cible le masquage par skip : une suite conditionnée par CC65 qui transforme un échec de compilation en test ignoré.
  • Donne des commandes pytest/pytest-cov concrètes et un paramètre --limit pour plafonner le nombre de constats.

Limites

  • Très lié à l'arborescence d'un dépôt précis (tests/, nes/, tracker/, compiler/, mappers/), donc peu portable tel quel.
  • Suppose pytest/pytest-cov et s'appuie sur des fichiers compagnons (_audit-common.md, _audit-severity.md) non fournis ici.
  • Produit une liste priorisée de tests à écrire, pas les tests eux-mêmes.
Quand l'utiliser

À utiliser avant les autres audits, pour vérifier que le filet de sécurité rattraperait bien les régressions introduites par les correctifs.

Quand l'éviter

À éviter sur un projet qui n'utilise pas pytest ou qui n'a pas l'arborescence et les fichiers d'audit référencés.

Analyse de sécurité

Prudence
Score qualité80/100

The skill is a project-specific test-suite audit that reads layout/severity convention files and runs the existing pytest suite plus coverage to map gaps. All commands shown (ls, pytest collect-only, coverage, targeted test runs) are benign and non-destructive, and the only write is a markdown audit report. It does trigger execution of project code and a C toolchain, so it warrants mild caution rather than a clean 'safe', but there is no meaningful risk of data loss or secret leakage.

Points d'attention
  • •Step 1 instructs running `python -m pytest`, `pytest --cov`, and targeted test modules, which execute arbitrary project code (including multiprocessing/`ProcessPoolExecutor` paths and a `ca65`/`ld65` toolchain); this is normal for a test audit but is real code execution.
  • •The skill writes output files into `docs/audits/AUDIT_REGRESSION_<TODAY>.md` — expected for an audit artifact, but it mutates the repo.
  • •No destructive, exfiltration, obfuscation, or safety-disabling content found; no attempts to override the auditor were present in the content.
  • •No allowed-tools declared, yet the instructions direct the agent to run shell commands (ls, python -m pytest, pytest --cov, and specific test invocations) against the target repository.
  • •Running the repository's own pytest/coverage toolchain can execute arbitrary project test code and write artifacts; this is legitimate but is a privileged action worth noting for a listing.
Comment cette analyse est faite

Exemples

Full suite audit
Audit the health of my test suite: find missing coverage on code that can break, assertions too weak to catch regressions, and stale or flaky tests. Give me a prioritized list of tests to add or strengthen.
Verify past fixes still hold
Re-verify that the regressions REG-08 through REG-11 are still fixed, and run the @requires_cc65 suites directly to confirm they actually PASS instead of skipping.
Cap the findings
Run the test-hygiene audit and report only the top 5 riskiest findings (--limit 5).

description: "Audit test-suite health — coverage gaps, weak assertions, flaky/stale tests" argument-hint: "[--limit <N>]"

Regression / Test-Hygiene Audit

Audit the test suite itself: where coverage is missing on code that can break, where tests assert too little to catch a regression, and where tests are stale, flaky, or testing the wrong thing. The output is a prioritized list of tests to add or strengthen — the safety net for every other audit's fixes.

Shared protocol: .claude/commands/_audit-common.md. Severity: .claude/commands/_audit-severity.md — a coverage gap on working code is LOW, but a gap on a path that has had bugs or that emits NES register data is MEDIUM (the blast radius is a silently-broken ROM).

A recent sprint closed ~100 issues, several against the test suite itself (REG-01 through REG-11 below, from AUDIT_REGRESSION_2026-06-28.md / -06-29.md). All are now fixed — don't re-report them — but re-verify status on every run: a fix landing doesn't guarantee it stays landed, and the skip-masking that REG-10/REG-11 removed (CC65-gated suites reporting real compile failures as "CC65 may not be installed" skips) is exactly the failure shape this audit exists to catch.

Parameters

  • --limit <N> — cap findings (report the top N by risk). Default: no cap.

Step 1: Inventory

ls tests/                                   # 52 test_*.py + conftest.py
python -m pytest -q --collect-only | tail -5
python -m pytest --cov=. --cov-report=term-missing -q   # if pytest-cov present

# The CC65-gated suites now use a real gate: `conftest.py:pytest_runtest_setup`
# skips @requires_cc65 only when ca65/ld65 are genuinely absent (via `shutil.which`),
# so with the toolchain present they compile real ROMs and assert unconditionally.
# Run them directly to confirm they PASS (not skip), don't just trust a green summary:
python -m pytest tests/test_ca65_export.py::TestCA65CompilationIntegration -v
python -m pytest tests/test_rom_validation_integration.py -v

Cross-reference tests/ against _audit-common.md § Project Layout: every subsystem should have at least one test module. Map test file → subsystem it covers.

Step 2: Coverage-Gap Dimensions

Dimension 1: Untested subsystems / modules

A source module with no corresponding test, or only an import-smoke test. Weight by risk: the NES-register path (nes/emulator_core.py, nes/pitch_table.py, nes/envelope_processor.py), the exporters, the mappers, and the compiler are the high-blast-radius gaps. A module with --cov < ~50% on a pipeline path is a finding.

Four modules previously sat at 36-71% coverage with no dedicated test module, plus one front-end (arranger/) with zero test references at all — all five closed, keep verifying the coverage didn't regress rather than re-flagging from scratch:

  • tracker/track_mapper.py — the legacy multi-track branch of assign_tracks_to_nes_channels (melody→pulse1, harmony→pulse2 w/ arpeggio, bass→triangle, drum-named→noise) was wholly unverified (REG-08/#48). Fixed by tests/test_track_mapper.py (synthetic 4-track + real-MIDI regression case); currently 71% cover.
  • compiler/cc65_wrapper.py — missing-tool detection (shutil.which → None), nonzero version-probe exit, stderr surfacing, and the full build() pipeline had no direct coverage (REG-09/#49). Fixed by tests/test_cc65_wrapper.py; currently 86%.
  • tracker/pattern_detector_parallel.py — the ProcessPoolExecutor map/merge, the fallback to EnhancedPatternDetector, and worker-count invariance were untested (REG-06/#46). Fixed by tests/test_pattern_detector_parallel.py; currently 78% (up from 36%).
  • mappers/factory.py / mappers/base.py — MapperFactory auto-select and each mapper's header/linker/capacity output were only exercised indirectly via test_ca65_export.py (REG-07/#47). Fixed by tests/test_mappers.py; factory.py now 74%, base.py 59% (residual gap: base.py's abstract-method stubs and a couple of capacity edge cases — worth a look, but no longer a blank gap).
  • arranger/ (role_analyzer.py, voice_allocator.py, gm_instruments.py, pipeline_integration.py) — the entire --arranger front-end had zero test references (REG-04/#44). Fixed by tests/test_arranger.py (role tagging, arpeggiation cadence, triangle no-duty invariant, frames-contract parity with process_all_tracks), plus tests/test_arranger_drum_detection.py, tests/test_arranger_frame_contract.py, and tests/test_voice_allocator.py (DPCM routing, noise-period routing). --arranger now also gets a real ROM-compile round trip (tests/test_e2e_pipeline.py::TestEndToEndPipeline::test_full_pipeline_arranger_mode, #129) — see Dimension 3.

Dimension 2: Weak assertions

Tests that run code but assert almost nothing — assert result is not None, assert len(x) > 0 where the values matter. The NES-output tests especially must assert exact register bytes / timer values, not just shape. Flag tests that would pass even if the music came out wrong.

Closed example, keep as the bar to hold other exporter tests to: several test_ca65_export.py / test_midi_parser_integration.py tests asserted only that a section/substring was present (header magic, "PATTERNS" in content) — would pass even with every note/timer/volume byte wrong (REG-05/#45). Fixed by a golden-bytes test class that parses test_midi/simple_loop.mid through the real pipeline and asserts the exact pulse1_sequence macro-bytecode stream and the first ntsc_period_low table bytes. Still worth a periodic grep for assertIn('PATTERNS' / bare-existence checks elsewhere in tests/test_ca65_export.py, tests/test_exporter_integration.py, and tests/test_famistudio_export.py — the golden-bytes fix only covers the one path it targeted.

Dimension 3: Round-trip / end-to-end gaps

The properties that unit tests miss: parse→...→ROM produces a valid ROM (tests/test_e2e_pipeline.py is the anchor); pattern compress→decompress equals the original; a generated .nes passes debug/check_rom.py. Missing round-trip coverage on compression is MEDIUM (it guards a CRITICAL failure mode).

  • Fixed: the 7-method TestCA65CompilationIntegration class in tests/test_ca65_export.py was RED against real ca65/ld65 — the shipped nes/audio_engine.asm had a relative branch (bcc @is_note) whose target was 130 bytes away, outside the 6502 ±127 range (REG-01/#39). Fixed by restructuring the dispatch (nes/audio_engine.asm:204-213, bcs+jmp instead of the out-of-range bcc) — confirmed: all 9 tests in TestCA65CompilationIntegration pass with the toolchain present. Re-verify this stays green; a relative-branch regression here is silent until an assembler is actually invoked.
  • Fixed (REG-10/#128): tests/test_rom_validation_integration.py is the designated "compile a real ROM and validate its bytes" gate. It previously had 5 of 9 tests SKIP even with ca65/ld65 present, because the hand-written music.asm fixture defined init_music:/update_music: as bare labels with no .export/.global, so ld65 failed with Unresolved external 'init_music' and an except Exception → pytest.skip masked it as "CC65 may not be installed". Fixed: the fixture now lives in tests/conftest.py's minimal_music_asm and emits .export init_music, update_music (matching the real exporter); the compile tests are gated with @pytest.mark.requires_cc65 (a real shutil.which gate in tests/conftest.py:pytest_runtest_setup) and each asserts compile_rom(...) unconditionally, and test_shared_music_asm_fixture_is_linkable pins the .export. Re-verify these PASS (not skip) with the toolchain present — a re-introduced except → skip here is exactly what this dimension catches.
  • Fixed (REG-15/#299): the two compile-failure (negative-path) tests in the same file (test_compilation_with_invalid_assembly, test_compilation_failure_without_rom_output) kept the except → pytest.skip("CC65 not installed") masking after REG-10 removed it from the compile-success tests, compounded by pass-either-way bodies. Now both are @requires_cc65 and assert compile_rom(...) is False plus no partial ROM. compile_rom() catches all failures and returns False (never raises), so any surviving except → skip in this file is dead masking — re-flag it.
  • Fixed (REG-11/#129): the anchor test_full_pipeline_midi_to_validated_rom (tests/test_e2e_pipeline.py) can now fail on a broken pipeline. The old try/except → pytest.skip, args.skip_validation = True, and if rom_path.exists(): guards are gone: the shared _run_pipeline helper sets skip_validation = False (validation ON) and _assert_valid_rom asserts unconditionally, all under a @pytest.mark.requires_cc65 gate. It also now exercises --arranger (test_full_pipeline_arranger_mode) and --no-patterns (test_full_pipeline_no_patterns_direct_export) through a real ca65/ld65 compile — closing the Dimension 1 arranger note above. Re-verify these assertions stay unconditional.

Dimension 4: Stale / wrong-target tests

Tests referencing renamed symbols, skipped/xfail tests with no tracking issue, tests that assert old (now-incorrect) behavior, or tests pinned to checked-in artifact files that have since changed. Confirm against current code.

Two closed examples worth knowing so they aren't re-flagged:

  • Four test classes in test_audio_fixes.py were @unittest.skip'd with "Obsolete: Assembly generation changed to MMC3 Macro Bytecode" and no tracking issue (REG-03/#42). All four actually targeted export_direct_frames (the direct/table-based export path, never removed) and still matched every assertion — the skip reason was stale, not the tests. Fixed: un-skipped; the one genuine failure (test_triangle_is_octave_above_pulse_period, a harmless pulse/16 vs triangle/32 integer-quantization drift) had its delta loosened from 0.05 to 0.08 to cover the observed max (~0.071) without masking a real regression.
  • verify_ca65_assembly() in tests/test_midi_parser_integration.py and the e2e assembly check asserted the old standalone .segment "HEADER" format the exporter no longer emits in macro-bytecode mode (REG-02/#40). Fixed (commit 184d3c9): verify_ca65_assembly (tests/test_midi_parser_integration.py:41-73) now branches on which export mode produced the content ("MMC3 Macro Bytecode" marker → asserts .segment "CODE_8000"/"BANK_00"/pulse1_sequence:/etc.; "Pattern Compressed" or standalone direct-frame → asserts the legacy HEADER/CODE/VECTORS sections, which are still correct for those modes). Note tests/test_e2e_pipeline.py:235's .segment "HEADER" assertion is not stale — it checks main.asm from NESProjectBuilder, a file the project builder (not the CA65 exporter) still always emits with a HEADER segment; don't conflate the two files when re-auditing this.

Recently-fixed stale-artifact case worth knowing: the hand-written music.asm fixture behind the ROM-validation tests (REG-10 above) had drifted from what the real exporter emits (missing the .export init_music, update_music directives) — the same failure shape this dimension exists to catch. Now fixed by moving it to tests/conftest.py's minimal_music_asm with the .export directives; don't re-flag it.

Dimension 5: Determinism / flakiness

Tests depending on multiprocessing scheduling (ParallelPatternDetector), dict/set ordering, wall-clock timing, or filesystem temp paths without isolation. These pass locally and fail in CI. Flag any test whose outcome can vary run-to-run.

Closed example: ParallelPatternDetector's determinism guarantee was not actually held — _select_best_patterns sorted candidates by score alone, so equal-score candidates resolved by ProcessPoolExecutor.as_completed arrival order, making which non-overlapping pattern won depend on host core count (REG-06/#46, same fix as Dimension 1). Fixed: a deterministic (start, length) tie-break was added, and tests/test_pattern_detector_parallel.py now pins identical patterns/references/ compression_ratio across max_workers=1/2/4 plus the pool-failure fallback. Re-run this test file specifically after any change to tracker/pattern_detector_parallel.py's scoring/selection — it's the one place a reintroduced non-determinism would surface.

Dimension 6: Fixture & isolation hygiene

Tests that write into the repo root instead of a temp dir, leak state between tests, or depend on a prior test having run. Check tests/conftest.py for shared fixtures (temp_dir, project_dir, minimal_midi_file, minimal_music_asm, valid_rom_file, and synthetic ROM-corruption fixtures) and whether they're used consistently — most compiler/ROM tests do use them. The music.asm fixture that had drifted from reality (missing .export directives) is now the shared minimal_music_asm fixture in tests/conftest.py, updated to emit .export init_music, update_music (see Dimension 3/4, REG-10) — re-verify the shared fixture is used consistently rather than re-hand-rolled per test.

Step 3: For each gap, specify the test

Don't just say "needs a test" — name the module, the property to assert, and the concrete input (a test_midi/ sample or a crafted JSON). A finding the /fix-issue pipeline can act on directly.

Output

Write to: docs/audits/AUDIT_REGRESSION_<TODAY>.md (YYYY-MM-DD). Structure:

  1. Coverage map — subsystem → test module(s) → rough coverage / gap.
  2. Findings — base format + Dimension; each names the test to add/strengthen.
  3. Prioritized backlog — the top tests to write first, by blast radius.

Then suggest:

/audit-publish docs/audits/AUDIT_REGRESSION_<TODAY>.md

Du fichier au premier essai

Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.

Consulter la source et ses fichiers annexes

Ce téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.

1. Télécharger le fichier

Télécharger SKILL.md

Depuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/regression-test-hygiene-audit-msih95o9/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.

Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.

mkdir -p '.skill-downloads/regression-test-hygiene-audit-msih95o9' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/regression-test-hygiene-audit-msih95o9/download' --output '.skill-downloads/regression-test-hygiene-audit-msih95o9/SKILL.md.part' && mv '.skill-downloads/regression-test-hygiene-audit-msih95o9/SKILL.md.part' '.skill-downloads/regression-test-hygiene-audit-msih95o9/SKILL.md'

2. Vérifier les prérequis

Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.

Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.

Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.

3. Charger la skill

Le fichier ne fournit pas un en-tête name/description exploitable pour cette procédure native. Le téléchargement reste disponible pour lecture ; utilisez le mode manuel ci-dessous.

Mode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.

4. Faire un premier essai

Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :

Lis .skill-downloads/regression-test-hygiene-audit-msih95o9/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.

Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.

Skills similaires