Passer au contenu
DedicatedPHP Contact

Tests de contrat pour les API PHP sans casser les intégrations

Apprenez à définir et automatiser des contrats vérifiables afin de faire évoluer des API PHP sans découvrir des incompatibilités une fois le changement déployé.

Schéma éditorial d’une API PHP validant les contrats de requêtes et de réponses avant le déploiement

Une API n’est pas définie uniquement par son contrôleur PHP ni par une spécification publiée. Elle l’est également par les attentes que d’autres systèmes ont déjà intégrées : une route, le nom d’un champ, un code HTTP, le format d’une erreur ou l’ordre nécessaire pour parcourir les pages. Les tests de contrat pour les API PHP transforment ces attentes en vérifications automatisées avant de fusionner du code ou de le déployer.

L’objectif n’est pas d’empêcher toute évolution. Il consiste à détecter si un changement modifie un accord observable par un consommateur et à le décider consciemment : maintenir la compatibilité, introduire une transition ou versionner l’interface. Cela est particulièrement important dans les API internes impliquant plusieurs équipes, les intégrations B2B et les flux asynchrones, où la défaillance peut apparaître des heures après la publication.

Ce que résolvent les tests de contrat et ce qu’ils ne remplacent pas

Ce que résolvent les tests de contrat et ce qu’ils ne remplacent pas — guía visual de DedicatedPHP

Un test de contrat vérifie que le fournisseur et le consommateur s’accordent sur une interaction : pour une requête valide donnée, le fournisseur produit une réponse avec une structure, des types et des règles convenus. Dans l’autre sens, un consommateur peut déclarer les requêtes dont il a besoin et le fournisseur vérifie qu’il peut y répondre.

Cette approche détecte des incompatibilités que les tests unitaires négligent souvent. Un test unitaire peut confirmer qu’un sérialiseur renvoie customer_id ; il ne démontre pas que le consommateur comprend toujours ce champ s’il attendait auparavant customerId. Un test d’intégration local peut couvrir l’endpoint, mais ne reflète pas nécessairement les hypothèses réelles de chaque intégration.

Ils ne remplacent pas les autres contrôles :

  • Tests unitaires, pour les règles de domaine, la validation et les transformations.
  • Tests d’intégration, pour la base de données, les files d’attente, le cache, l’authentification ou les services connectés.
  • Tests end-to-end, pour des parcours critiques complets dans des environnements contrôlés.
  • Tests de sécurité et de performance, pour l’autorisation, les abus, l’exposition des données, la latence et la capacité.
  • Observabilité en production, pour détecter les consommateurs qui utilisent encore des comportements en cours de retrait.

Un contrat ne certifie pas non plus que la réponse est correcte du point de vue métier ; il certifie qu’elle préserve la forme et la sémantique déclarées. Il doit donc s’accompagner d’exemples qui expriment des règles pertinentes, et pas seulement de schémas vides.

Ce qui fait partie du contrat d’une API

Le contrat correspond à tout comportement qu’un consommateur peut observer et dont il dépend. Le limiter au JSON d’une réponse réussie exclut les ruptures les plus fréquentes. Pour chaque opération, il est préférable de convenir au minimum des éléments suivants.

  • Requête : méthode, route, paramètres de requête, en-têtes, corps, champs obligatoires, formats et limites.
  • Réponse : code HTTP, en-têtes pertinents, structure, types, champs facultatifs, champs qui acceptent des valeurs nulles et formats de date, de devise ou d’identifiants.
  • Erreurs : codes de statut, corps d’erreur, code fonctionnel stable et conditions qui le génèrent. Un texte destiné aux personnes peut changer ; un code tel que validation_failed est plus adapté à l’automatisation.
  • Pagination et filtrage : signification de limit, du curseur ou de la page, stabilité de l’ordre, représentation du curseur suivant et traitement des ensembles vides.
  • Authentification et autorisation : mécanisme accepté, en-têtes requis, scopes ou permissions, et différence entre identifiants invalides, absence d’identifiants et accès refusé.
  • Événements et webhooks : nom de l’événement, version ou schéma du payload, signature, tentatives de nouvelle livraison, identifiant d’événement, ordre non garanti et attentes d’idempotence.

Le caractère obligatoire d’une propriété et l’acceptation de null sont des règles indépendantes. Un champ peut être obligatoire et accepter null, facultatif et ne pas l’accepter lorsqu’il est présent, ou facultatif et l’accepter s’il est présent. De même, une propriété omise, une propriété présente avec null et une propriété avec une chaîne vide sont des états distincts. Si le consommateur les interprète différemment, le contrat doit l’exprimer et le tester.

De petits changements qui peuvent casser les consommateurs

Une modification peut sembler anodine du point de vue du fournisseur et être incompatible pour un client généré, un validateur strict ou une logique métier. Changer un entier en chaîne, par exemple 42 en "42", casse les comparaisons et les schémas. Rendre un champ facultatif ne détermine pas à lui seul s’il accepte null : la première règle définit si la propriété doit être présente, tandis que la seconde définit les valeurs valides lorsqu’elle est incluse.

D’autres changements risqués consistent à renvoyer 200 là où 201 était auparavant renvoyé, à remplacer une liste vide par null, à modifier la précision d’un décimal, à renommer un code d’erreur ou à modifier l’ordre de pagination sans prévenir. Ajouter un champ est généralement compatible pour des lecteurs tolérants, mais ne l’est pas si un consommateur valide un schéma fermé ou calcule des signatures sur le corps complet.

La compatibilité dépend de l’accord réel, et non d’une règle isolée. Il convient de classifier chaque changement selon les consommateurs connus, la tolérance déclarée et la criticité du flux. Si cette information n’est pas connue, elle doit être traitée comme un risque et non comme une hypothèse favorable.

Choisir entre spécification, contrats de consommateur ou les deux

Une spécification d’interface, par exemple une description OpenAPI, fonctionne bien comme source commune pour les routes, les opérations, les paramètres, les schémas et les réponses. Elle peut être validée dans le pipeline afin de détecter les changements incompatibles par rapport à une version de référence. Elle est utile lorsqu’il y a de nombreux consommateurs ou que des clients et de la documentation sont générés à partir de la même définition.

Cependant, un schéma ne capture pas toujours ce qui importe à chaque consommateur : des combinaisons de filtres, une erreur spécifique face à une condition métier ou une dépendance à une valeur d’exemple. Les contrats pilotés par les consommateurs déclarent les interactions concrètes dont chaque consommateur a besoin. Le fournisseur les vérifie par rapport à son implémentation.

Utiliser les deux niveaux est souvent raisonnable : la spécification régit la surface générale et les contrats de consommateur couvrent les flux à forte valeur ou la sémantique difficile à réduire à un schéma. Une responsabilité claire doit exister pour chaque artefact. Si une spécification n’est pas mise à jour lorsque le code change, elle cesse d’être une source de vérité et devient une documentation fictive.

Exemples représentatifs et cas limites

Un exemple de contrat doit contenir des données réalistes dans leur structure, et non des données de production. Pour une ressource de commande, incluez un cas avec des éléments, un cas vide s’il est valide, des identifiants au format convenu et des dates complètes avec fuseau horaire lorsque telle est la convention. Ajoutez des cas d’autorisation refusée, de validation échouée, de ressource inexistante et de pagination finale.

Évitez de figer des détails non pertinents qui changent légitimement, comme un identifiant aléatoire, l’heure actuelle ou l’ordre des propriétés JSON. Utilisez des assertions précises sur ce qui est stable et une tolérance explicite sur ce qui est variable. Chaque exemple doit répondre à un besoin connu ; une énorme collection de réponses inventées augmente la maintenance sans accroître la confiance.

Mise en œuvre progressive dans une API PHP existante

Il n’est pas nécessaire de modéliser toute l’API avant d’en tirer de la valeur. Commencez par un inventaire des consommateurs : applications internes, clients B2B, processus batch, applications mobiles, automatisations et récepteurs de webhooks. Enregistrez le propriétaire, le canal de contact, l’opération utilisée, la criticité et la capacité de mise à jour.

Ensuite, priorisez les endpoints qui créent ou modifient des ressources, authentifient des utilisateurs, alimentent des processus financiers ou déclenchent des automatisations. Établissez une référence de leur comportement actuel au moyen d’une spécification révisée et de tests contre une instance reproductible de l’API. En PHP, le test doit exercer la véritable couche HTTP de l’application, et non invoquer directement une classe de service : le contrat inclut le routage, les middlewares, la sérialisation et la gestion des exceptions.

POST /api/orders
Authorization: Bearer token
Content-Type: application/json

{"items":[{"sku":"ABC-1","quantity":2}]}

201 Created
{"id":"ord_123","status":"pending","items":[...]}

L’exemple précédent n’est utile que s’il est accompagné de règles : quels champs sont obligatoires, si id est toujours une chaîne, quelles erreurs un SKU invalide renvoie et si l’état initial est garanti. Ce sont ces règles qui doivent être transformées en assertions.

Validation en intégration continue et avant le déploiement

Le pipeline doit échouer avant la fusion si l’implémentation ne respecte pas les contrats approuvés. Un flux pratique comprend l’exécution des tests unitaires, le démarrage de dépendances contrôlées, le lancement de l’API PHP avec une configuration de test et la validation de la spécification, des contrats de fournisseur et des contrats de consommateurs représentatifs. Les tests doivent utiliser des données isolées et déterministes afin qu’un échec soit reproductible.

Dans une demande de changement, comparez également la spécification proposée avec la version publiée afin de signaler les suppressions de routes, le renforcement des exigences de présence, les changements dans l’acceptation de null, les changements de type et les réponses supprimées. Le diagnostic doit indiquer l’opération, l’interaction et la règle non respectée ; une simple erreur de schéma impose trop d’investigation.

Avant le déploiement, exécutez la même suite sur l’artefact qui sera publié, et non sur une compilation différente. Après le déploiement, surveillez les codes d’erreur, les échecs de désérialisation signalés par les clients, l’utilisation des versions et le trafic vers les routes dépréciées. La validation préalable réduit le risque ; elle ne remplace pas la confirmation du comportement sous trafic réel.

Compatibilité, dépréciation et retrait sûr

Compatibilité, dépréciation et retrait sûr — guía visual de DedicatedPHP

Lorsqu’un changement n’est pas compatible, privilégiez une transition explicite. Vous pouvez ajouter un nouveau champ tout en conservant l’ancien, introduire une nouvelle opération ou version, et communiquer une date de retrait étayée par des signaux d’usage. La dépréciation est une période opérationnelle avec des propriétaires, de la communication et de la mesure ; ce n’est pas seulement une note dans la documentation.

Ne retirez pas un comportement parce qu’un délai s’est écoulé si vous ne pouvez pas identifier les consommateurs en attente ou si le flux est critique. Lorsque cela est possible, exposez des avertissements contrôlés et des métriques pour localiser les usages anciens sans modifier la réponse. L’exposition progressive d’une nouvelle version permet d’observer les erreurs et de corriger les contrats avant d’étendre le changement.

Les erreurs les plus courantes consistent à ne tester que les réponses réussies, à modéliser des messages destinés aux personnes plutôt que des codes d’erreur, à supposer que tous les clients ignorent les nouveaux champs et à ne pas impliquer de consommateurs réels ou représentatifs. Les tests de contrat apportent de la valeur lorsqu’ils reflètent des accords maintenus par les deux parties et sont exécutés comme une condition normale de livraison.

Vous souhaitez appliquer ces idées à votre projet ?Parlons de votre plateforme PHP.
Afficher les services associés