jobsphp

Test technique PHP : les erreurs de mon dernier échec

Carrières & Recrutement. Test technique PHP : les erreurs de mon dernier échec

PHP 8.2 n’a plus que des correctifs de sécurité jusqu’au 31 décembre 2026. Et pourtant, dans les tests techniques, on continue de croiser des solutions qui traitent le langage comme s’il était resté…

Test technique PHP: les erreurs de mon dernier échec

PHP 8.2 n’a plus que des correctifs de sécurité jusqu’au 31 décembre 2026. Et pourtant, dans les tests techniques, on continue de croiser des solutions qui traitent le langage comme s’il était resté bloqué quelque part entre PHP 5.6 et la grande époque des contrôleurs de 900 lignes. C’est là que le paradoxe devient savoureux: on recrute un senior pour sécuriser, faire évoluer et transmettre… puis on lui soumet un exercice où deux heures de code doivent raconter dix ans de réflexes.

Mon dernier échec de test technique PHP ne se résume pas à une ligne rouge dans un outil ou à un recruteur qui aurait coché une case secrète. Il n’existe pas de barème universel, pas de liste officielle des péchés capitaux du senior PHP en France. Mais le débriefing est assez limpide: un test ne mesure pas seulement si l’application « marche ». Il regarde ce qu’on laisse sous le capot. Les hypothèses silencieuses. Les dettes qu’on introduit à toute vitesse. Les détails que l’on prétend connaître… jusqu’au moment où ils deviennent concrets.

Les erreurs de test technique PHP en recrutement senior sont rarement spectaculaires. Personne ne renverse un serveur de production depuis son salon. Elles sont plus insidieuses: un type qui n’est décoratif qu’en apparence, une dépendance installée les yeux fermés, un test vert qui ne vérifie rien, un code lisible uniquement par la personne qui l’a écrit à 23 h 47. Le genre de petites choses qui font dire à une équipe: « OK, ça compile. Mais est-ce qu’on a envie de maintenir ça ensemble lundi matin? »

Le typage suffit-il vraiment à faire “senior”?

Non. Et heureusement, parce que taper string devant trois arguments ne transforme pas une méthode fragile en contrat solide. Le typage en PHP est devenu beaucoup plus riche: paramètres et retours typés, propriétés typées depuis PHP 7.4, constantes de classe typées depuis PHP 8.3… On a les outils. Reste à ne pas les employer comme des stickers sur un vieux frigo legacy.

L’erreur classique dans un test technique PHP raté, c’est d’afficher une signature rassurante tout en laissant le comportement réel flotter dans le brouillard. Exemple typique: une méthode accepte un identifiant, construit une requête, retourne parfois un objet, parfois null, parfois un tableau d’erreur bricolé parce que « c’est pratique ». Techniquement, le code peut avancer. Fonctionnellement, le contrat vient de prendre feu.

Le niveau senior se voit plutôt dans les questions qu’on se pose avant même de coder:

  • Que représente vraiment cette valeur: une chaîne libre, un identifiant métier, une date, un montant, un statut?
  • L’absence de donnée est-elle un cas normal (null) ou une erreur métier qui mérite une exception dédiée?
  • Que doit pouvoir déduire l’appelant sans relire toute l’implémentation?
  • Le type reflète-t-il le domaine ou cache-t-il une collection hétérogène derrière un banal array?
  • Est-ce que je peux expliquer le comportement de cette API à quelqu’un qui arrive dans six mois sur le projet?

Le piège, évidemment, porte un nom: declare(strict_types=1). Beaucoup le posent au sommet d’un fichier comme un drapeau planté sur la Lune, puis s’imaginent que tout le projet passe magiquement en mode strict. Nope.

En PHP, le mode strict est défini fichier par fichier. Plus précisément, il dépend du fichier depuis lequel l’appel est effectué. Une fonction déclarée dans un fichier strict peut toujours recevoir des conversions scalaires si elle est appelée depuis un fichier qui ne déclare pas le mode strict. Oui, c’est subtil. Oui, c’est exactement le genre de subtilité qu’un exercice senior peut révéler sans jamais poser la question frontalement.

Le typage ne sert pas à faire joli dans une signature: il sert à rendre les mauvaises hypothèses bruyantes, tôt, et au bon endroit.

Avec le mode strict, une valeur doit correspondre au type déclaré, à l’exception habituelle de l’entier accepté pour un paramètre float. Sans lui, PHP peut convertir certaines valeurs scalaires lorsqu’il le peut. Dans un petit exercice isolé, cette différence semble cosmétique. Dans une application qui reçoit des données de formulaires, d’API ou de files de messages, elle change complètement la manière dont les bugs voyagent.

Pour une préparation de test technique Symfony senior, je conseille d’ailleurs de ne pas réciter une doctrine du « tout strict partout ». Le recruteur ne cherche pas forcément un évangéliste du strict_types — même si, bon, on connaît tous le plaisir d’un bug qui explose proprement au lieu de se déguiser en comportement métier. Il cherche une personne qui comprend la frontière: où les données entrent, où elles sont normalisées, où le domaine impose ses règles et où une erreur doit être visible.

Pourquoi les dépendances racontent-elles autant de choses sur nous?

Parce qu’un composer require lancé à toute vitesse, c’est parfois l’équivalent PHP de commander une pièce inconnue sur Internet et de l’installer dans une voiture sans ouvrir le capot. Ça peut fonctionner. Jusqu’au moment où non.

Dans un test de recrutement informatique, on ne demande pas forcément de bâtir une stratégie de sécurité complète. Le temps est compté, l’environnement est artificiel, et personne de raisonnable n’attend un audit de conformité à chaque ligne. En revanche, introduire une bibliothèque pour éviter quinze lignes de code, ignorer son état ou livrer un verrouillage de versions incohérent envoie un signal très concret: quelle relation cette personne entretient-elle avec sa chaîne de dépendances?

La commande composer audit mérite de faire partie des réflexes. Elle vérifie les dépendances installées à partir des avis de sécurité, mais elle ne s’arrête pas là: elle détecte aussi les paquets abandonnés, ceux signalés comme malveillants et les règles de dépendances configurées. Un retour de code 0 signifie qu’aucun problème n’a été trouvé; 1 indique notamment un problème de politique ou des paquets requis absents.

Ce n’est pas un tampon « application sécurisée ». Ce serait beaucoup trop simple — et donc très hype, mais faux. C’est un filet élémentaire, celui qu’on pose avant de prétendre avoir fait du travail sérieux.

Réflexe expédiéRéflexe de seniorCe que cela montre
Ajouter un paquet pour une seule fonctionÉvaluer si le besoin justifie une dépendanceSens de la surface de maintenance
Installer sans regarder les versionsComprendre la contrainte de version et le verrouillageMaîtrise de la reproductibilité
Considérer que le projet tourne donc qu’il est sainLancer un audit des dépendancesAttention à la sécurité courante
Ignorer un paquet abandonnéDécider de le remplacer, de l’isoler ou de documenter le risqueCapacité à arbitrer sans paniquer
Mettre des secrets dans une configuration livréeUtiliser des variables d’environnement ou des valeurs facticesRespect des frontières de sécurité

Il y a aussi une erreur moins technique, mais beaucoup plus gênante: transmettre des identifiants, des clés ou du code propriétaire d’un ancien employeur pour « enrichir » sa réponse. Ce n’est pas une preuve de maturité. C’est un drapeau rouge avec gyrophare intégré. Un bon exercice se résout avec des données fictives, des exemples neutres et une explication claire des hypothèses.

Le recruteur attentif ne demande pas un bunker. Il regarde si on sait distinguer une démo propre d’un raccourci dangereux. Ce n’est pas la même chose.

Un test vert veut-il dire que les tests sont bons?

Ah, les tests verts. Le petit feu de circulation qui donne l’impression que tout est sous contrôle. Sauf que PHPUnit n’applaudit pas nécessairement la qualité: il exécute ce qu’on lui demande d’exécuter. Et si on ne lui demande presque rien, il peut très bien nous laisser repartir avec une fausse confiance parfaitement emballée.

PHPUnit considère par défaut comme risqué un test qui n’exécute aucune assertion et ne configure aucune attente sur un objet simulé. C’est une information précieuse dans un test technique: un test qui appelle une méthode sans vérifier son résultat, ce n’est pas de la couverture utile. C’est une démonstration de présence.

Le sujet des doublures est tout aussi révélateur. Un bouchon sert à contrôler une valeur retournée. Un objet simulé, lui, sert notamment à vérifier des interactions. La nuance n’est pas universitaire: elle évite de transformer tous les tests en théâtre d’ombres où l’on imite chaque appel interne au lieu de décrire un comportement.

Et là, attention au vieux réflexe qui traîne dans pas mal de snippets copiés-collés: l’utilisation de with() sur un bouchon est dépréciée depuis PHPUnit 12.5.11 et ne fonctionnera plus avec PHPUnit 13. Ce n’est pas grave de ne pas avoir mémorisé chaque note de version. Ce qui devient gênant, c’est de présenter une API vieillissante comme une évidence sans regarder l’outillage du projet.

Dans un exercice court, on ne peut pas tester l’univers entier. On peut en revanche choisir les scénarios qui prouvent qu’on a compris le métier:

1. Le chemin nominal, parce qu’il confirme que la règle demandée est réellement implémentée, pas seulement dessinée dans les noms de méthodes.

2. La frontière métier, parce que les erreurs vivent là: montant nul, statut interdit, ressource introuvable, transition d’état impossible.

3. L’entrée imparfaite, parce qu’une application reçoit rarement les données idéales qu’on lui a offertes dans son propre test.

4. L’effet de bord décisif, lorsqu’il existe: un message envoyé, une écriture déclenchée, un service externe appelé une seule fois avec la bonne intention.

5. La non-régression la plus probable, celle qui deviendra fragile dès que quelqu’un refactorera le code dans trois semaines.

Un test senior ne prouve pas qu’une méthode a été appelée. Il prouve qu’un comportement utile survivra au prochain refacto.

Le défaut que j’ai vu le plus souvent — chez les autres comme chez moi, pas de posture de donneur de leçons ici — consiste à trop tester la plomberie. On vérifie que le contrôleur appelle le service, que le service appelle le dépôt, que le dépôt reçoit exactement tel objet, puis on a une suite de tests très bavarde et très cassante. À la moindre réorganisation interne, tout tombe, même si la règle métier continue de fonctionner.

Dans un test technique PHP, mieux vaut quelques cas lisibles, nommés avec précision, qu’une collection de scénarios dont personne ne comprend la valeur. Le recruteur peut relire ce code. Votre futur collègue aussi. Et si le test est impossible à expliquer, c’est rarement bon signe.

PSR-12, détail de forme ou signal de collaboration?

On a tous entendu la phrase: « Le style, je peux le corriger avec un outil. » C’est vrai. En partie. Un formateur peut déplacer les accolades, harmoniser les espaces et calmer une ligne qui joue à Tetris. Mais il ne peut pas toujours réparer une classe qui fait cinq métiers, des noms flous ou des conditions emboîtées comme des poupées russes.

PSR-12 étend et remplace PSR-2. Son objectif est très terre-à-terre: réduire la friction de lecture entre auteurs grâce à des règles de formatage partagées. Ce n’est pas une loi du langage PHP, ni une obligation universelle de recrutement. Une équipe peut adopter une autre convention, un outil différent, ou une base legacy avec ses propres cicatrices.

Mais dans un test, la lisibilité est un accélérateur de confiance. Quand le code est net, les arbitrages deviennent visibles. Quand les noms portent le métier, les commentaires deviennent moins nécessaires. Quand la structure est prévisible, le reviewer peut consacrer son énergie aux décisions importantes plutôt qu’à deviner ce que signifie $data2.

Les erreurs à éviter en entretien technique dev ne sont donc pas seulement syntaxiques. Elles sont souvent éditoriales:

  • une méthode process() qui fait validation, persistance, calcul, notification et gestion d’erreur;
  • un tableau associatif qui circule entre quatre couches sans contrat explicite;
  • un commentaire qui répète le code au lieu d’expliquer une décision;
  • une exception attrapée puis silencieusement avalée « pour que ça passe »;
  • une classe de service transformée en couteau suisse parce que l’exercice semblait petit;
  • une convention de nommage qui change à chaque fichier, comme si plusieurs personnes avaient codé en relais.

Le niveau senior ne consiste pas à surarchitecturer une todo-list de recrutement avec quatorze interfaces, trois fabriques et un bus d’événements pour calculer une TVA. Pitié. Le game changer, c’est l’ajustement: assez de structure pour que le code soit extensible, pas assez de cérémonial pour masquer l’absence de décision.

Un exercice bien rendu peut d’ailleurs contenir un court fichier de notes — pas un roman, juste quelques lignes — pour préciser les choix: version de PHP supposée, commande de lancement, compromis volontaire, point que l’on traiterait avec plus de temps. C’est extrêmement utile quand l’énoncé est ambigu. Et les énoncés ambigus, dans le recrutement tech, ne sont pas exactement une espèce en voie d’extinction.

Que cherche vraiment le recruteur derrière l’exercice?

Pas un clone de son dépôt. Pas une personne capable de deviner ses conventions à distance par télépathie Symfony. Un test de compétences complète le CV et l’entretien: il peut prendre la forme d’un cas pratique ou d’un questionnaire, mais son intérêt est de rendre le raisonnement observable.

Alors, oui, une solution fonctionnelle compte. Mais le recruteur regarde aussi la manière dont on travaille avec l’incertitude.

Quand une consigne manque de précision, le mauvais réflexe consiste à inventer silencieusement une règle puis à la défendre comme si elle avait été écrite dans le marbre. Le bon réflexe: énoncer l’hypothèse. « J’ai considéré que tel statut est terminal. » « J’ai choisi de retourner une erreur métier plutôt qu’un null parce que… » « J’ai isolé l’accès HTTP pour pouvoir le tester. » Voilà du signal. Voilà de la matière pour un entretien.

Il y a enfin un sujet que l’on oublie trop souvent dans le débriefing de test technique recrutement informatique: les données de candidature. Les informations demandées doivent être adéquates, pertinentes et strictement nécessaires à la sélection et au déroulement de l’entretien. Pour les candidatures non retenues, un dossier peut être conservé jusqu’à deux ans, dans le cadre prévu et sauf demande d’effacement de la personne concernée.

Cela veut dire deux choses très simples. Côté entreprise: ne pas transformer l’exercice en collecte de données ou en travail gratuit déguisé. Côté candidat: ne pas remplir un dépôt de données réelles, de secrets ou d’éléments confidentiels pour paraître plus crédible. Une solution propre protège aussi ce qu’elle ne montre pas.

Rater un test, puis faire mieux que le suivant

Le vrai piège après un échec, c’est de répondre par une préparation mécanique: réciter les nouveautés de PHP, empiler les kata, apprendre par cœur la dernière syntaxe de PHPUnit et prier pour tomber sur le bon sujet. Ça aide, évidemment. Mais ce n’est pas le cœur du problème.

Le cœur, c’est de reprendre ses automatismes. Est-ce qu’on laisse passer des conversions implicites sans les voir? Est-ce qu’on installe des paquets pour gagner dix minutes et créer six mois de maintenance? Est-ce qu’on écrit des tests pour faire monter un compteur ou pour protéger une règle? Est-ce que notre code raconte clairement ce qu’il fait?

PHP 8.3 reste couvert pour la sécurité jusqu’à fin 2027, PHP 8.4 jusqu’à fin 2028 et PHP 8.5 jusqu’à fin 2029. Le langage avance, les outils avancent, les pratiques aussi. Ce qui vieillira le plus vite, ce n’est pas notre capacité à retenir une nouveauté: c’est notre habitude de croire qu’un code qui passe est nécessairement un code qui tient.

Un test technique ne devrait pas être un tribunal. C’est un instantané, imparfait, sous contrainte. Mais il peut devenir un excellent miroir. Et si l’on sait lire ce miroir sans se raconter d’histoires, le prochain exercice ne sera peut-être pas parfait — personne n’a besoin de cette fiction-là — mais il sera plus net, plus solide, plus facile à défendre.

Et ça, franchement, c’est déjà une sacrée refacto de carrière.

Questions fréquentes

Pourquoi le mode strict (declare(strict_types=1)) ne suffit-il pas à sécuriser tout un projet ?
Le mode strict est défini fichier par fichier et ne s'applique qu'au fichier où il est déclaré. Une fonction peut toujours recevoir des conversions scalaires si elle est appelée depuis un fichier qui n'active pas cette directive.
Quelle est la différence entre un bouchon (stub) et un objet simulé (mock) dans un test ?
Un bouchon sert uniquement à contrôler une valeur retournée, tandis qu'un objet simulé est utilisé pour vérifier les interactions entre les composants.
Pourquoi est-il déconseillé d'utiliser la méthode with() sur un bouchon dans PHPUnit ?
L'utilisation de with() sur un bouchon est dépréciée depuis la version 12.5.11 de PHPUnit et ne sera plus supportée dans la version 13.
À quoi sert la commande composer audit ?
Elle permet de vérifier les dépendances installées en les comparant aux avis de sécurité, tout en détectant les paquets abandonnés, malveillants ou les problèmes de configuration.
Comment bien gérer une consigne ambiguë lors d'un test technique ?
Le meilleur réflexe est d'énoncer clairement ses hypothèses et ses choix de conception dans un court fichier de notes accompagnant le code.