Exécuter les tests¶
Vous êtes développeur·se sur Kirexo et vous voulez lancer la suite de tests pour vérifier que vos modifications n'ont rien cassé.
Prérequis¶
- La stack Docker est démarrée (
castor docker:up). - Les dépendances sont installées (
castor install).
Préparation automatique de la BDD¶
À chaque démarrage de PHPUnit — quel que soit le chemin d'exécution (castor test:unit, castor test:e2e, castor test:coverage, ou même un vendor/bin/phpunit direct depuis le conteneur) — tests/bootstrap.php enchaîne trois commandes Doctrine avant que le premier test ne tourne :
doctrine:database:create --if-not-exists— crée la baseapp_testsi elle n'existe pas (idempotent, ne touche à rien sinon).doctrine:migrations:migrate --allow-no-migration— applique les migrations en attente, tolère l'absence de migration sur une base neuve.doctrine:fixtures:load— purge la base puis recharge toutes les fixtures auto-tagguéesdoctrine.fixture.orm(les classes desrc/DataFixtures/étendantDoctrine\Bundle\FixturesBundle\Fixturesont enregistrées automatiquement comme services).
Conséquence pratique : vous n'avez plus à charger les fixtures à la main avant de lancer les tests. Le bootstrap garantit un état déterministe à chaque session PHPUnit.
Articulation avec DAMADoctrineTestBundle
DAMADoctrineTestBundle enveloppe chaque test dans une transaction roll-backée à la fin du test. Les fixtures chargées par le bootstrap sont committées hors transaction (avant que la machinerie de PHPUnit ne démarre), donc elles restent visibles pour tous les tests de la session. Toute mutation faite par un test est annulée avant le suivant — pas besoin de recharger les fixtures entre deux tests.
Et castor fixtures:load alors ?
La cible castor fixtures:load reste utile en dehors du contexte des tests : rejouer manuellement les fixtures sur la base de dev (app, pas app_test) pour repartir d'un état propre lors d'une exploration locale. Pas besoin de l'appeler avant castor test:unit ni castor test:e2e.
Tests unitaires et d'intégration¶
Cette cible lance PHPUnit sur les suites unit et integration. Elle s'exécute dans le conteneur php — vous n'avez rien à installer côté hôte. La BDD app_test est préparée automatiquement (voir Préparation automatique de la BDD).
Pour filtrer sur un test précis, passez un --filter :
Tests E2E¶
Les tests E2E utilisent Panther, qui pilote un vrai navigateur headless. Ils sont nettement plus lents que les tests unitaires — lancez-les après que le reste de la suite est passée.
Ce que testent les tests E2E
Panther ne teste que le rendu et la navigation : est-ce que la page se charge, est-ce que le formulaire se soumet, est-ce que le Live Component se met bien à jour. La logique métier est couverte par les tests unitaires et d'intégration — dupliquer cette logique dans des tests navigateur serait lent et fragile.
Couverture¶
Pour un rapport de couverture textuel :
Par défaut, la cible couvre les suites unit + integration. Ajoutez --with-e2e pour inclure la suite Panther dans la mesure, ou --html pour générer un rapport HTML dans var/coverage/ (cf. castor test:coverage).
La cible de couverture attendue est de 100 % sur les controllers, handlers, services, repositories, voters et composants Twig/Live (voir CLAUDE.md).
Deux gates de couverture imposés en CI
En local, castor test:coverage ne fait qu'afficher la couverture. C'est la pipeline GitLab qui bloque : le job test:unit mesure la couverture des suites unit + integration et fait échouer la pipeline si l'un des deux seuils n'est pas tenu.
- Lignes :
100,00 %— gate historique de la règleCLAUDE.md. - Branches :
≥ 90,00 %— chaque condition doit être testée dans ses deux sens (une ligne exécutée ne vaut pas une branche couverte).
Détail des deux gates : Pipeline GitLab CI — Tests et gate de couverture.
Conséquence du choix de mesurer la couverture sur unit + integration uniquement : un controller invokable trivial couvert seulement par son E2E (cas autorisé par CLAUDE.md) doit aussi être couvert en integration (un test WebTestCase), sinon il fait tomber test:unit sous 100 %.
Ne pas désactiver native_function_invocation dans PHP-CS-Fixer
Depuis PHP 8.4, chaque appel à une fonction interne (trim, preg_match, implode, str_starts_with…) émis dans un namespace non global compile en opcode JMP_FRAMELESS avec un fast path (la fonction interne attendue) et un slow path (une fonction de même nom redéfinie dans le namespace courant). Cobertura compte le slow path comme une branche non couverte — sans intervention, le gate branches ≥ 90 % devient inatteignable dès qu'un fichier appelle quelques fonctions internes.
La parade est portée par PHP-CS-Fixer dans .php-cs-fixer.dist.php :
Le preset @internal couvre toutes les fonctions internes (≈ 1 700, contre ≈ 40 pour @compiler_optimized). À chaque castor cs:fix, les appels sont préfixés par \ (\trim(...), \preg_match(...)…) ; le compilateur PHP les résout alors statiquement en FRAMELESS_ICALL direct et le slow path disparaît du bytecode — donc du rapport branches.
À retenir :
- Vous n'écrivez jamais
\trim()à la main. C'est cs-fixer qui s'en charge au prochaincastor cs:fix. - Ne pas retirer la règle
native_function_invocationdu fichier de config : le gate branches tomberait avec. - Si vous voyez la couverture branches chuter brutalement après un refactor, lancez
castor cs:fixavant d'investiguer plus loin.
Organisation des tests¶
L'arborescence tests/ reflète exactement celle de src/ :
tests/
Unit/ # Tests unitaires — symétrique de src/
Integration/ # Tests d'intégration — flows multi-fichiers, base réelle
E2E/ # Tests Panther — parcours navigateur uniquement
Un fichier src/Handler/PublishArticleHandler.php est testé par tests/Unit/Handler/PublishArticleHandlerTest.php. Cette symétrie rend la navigation triviale et permet de repérer instantanément un fichier non testé.
Voir aussi¶
- Commandes Castor — inventaire complet des cibles disponibles.
- Architecture — la séparation CQRS a un impact direct sur la manière d'organiser les tests.