Ga direct naar de inhoud
DedicatedPHP Contact

Contracttests voor PHP-API's zonder integraties te breken

Leer verifieerbare contracten definiëren en automatiseren om PHP-API's te ontwikkelen zonder incompatibiliteiten pas na de uitrol te ontdekken.

Redactioneel diagram van een PHP-API die contracten voor verzoeken en antwoorden valideert vóór de uitrol

Een API wordt niet alleen gedefinieerd door de PHP-controller of een gepubliceerde specificatie. Ook de verwachtingen die andere systemen al hebben ingebouwd definiëren haar: een route, de naam van een veld, een HTTP-code, het formaat van een fout of de benodigde volgorde om pagina's te doorlopen. Contracttests voor PHP-API's zetten deze verwachtingen om in geautomatiseerde controles voordat code wordt samengevoegd of uitgerold.

Het doel is niet om elke evolutie te verhinderen. Het is om vast te stellen of een wijziging een voor een afnemer waarneembare afspraak verandert en daar bewust over te beslissen: compatibiliteit behouden, een overgang invoeren of de interface versioneren. Dit is vooral belangrijk voor interne API's met meerdere teams, B2B-integraties en asynchrone werkstromen waarin de storing uren na de publicatie kan optreden.

Wat contracttests oplossen en wat ze niet vervangen

Wat contracttests oplossen en wat ze niet vervangen — guía visual de DedicatedPHP

Een contracttest controleert dat aanbieder en afnemer overeenstemmen over een interactie: bij een geldig verzoek produceert de aanbieder een antwoord met een afgesproken structuur, typen en regels. Omgekeerd kan een afnemer verklaren welke verzoeken nodig zijn en verifieert de aanbieder dat hij hieraan kan voldoen.

Deze aanpak detecteert incompatibiliteiten die unit tests doorgaans over het hoofd zien. Een unit test kan bevestigen dat een serializer customer_id retourneert; hij toont niet aan dat de afnemer dat veld nog begrijpt als die eerder customerId verwachtte. Een lokale integratietest kan het endpoint afdekken, maar bevat niet noodzakelijk de werkelijke aannames van elke integratie.

Ze vervangen andere controles niet:

  • Unit tests, voor domeinregels, validatie en transformaties.
  • Integratietests, voor database, wachtrijen, cache, authenticatie of gekoppelde diensten.
  • End-to-endtests, voor volledige kritieke werkstromen in gecontroleerde omgevingen.
  • Beveiligings- en prestatietests, voor autorisatie, misbruik, blootstelling van gegevens, latentie en capaciteit.
  • Observeerbaarheid in productie, om afnemers te detecteren die nog afhankelijk zijn van gedrag dat wordt uitgefaseerd.

Een contract certificeert evenmin dat het antwoord functioneel correct is; het bevestigt dat het de vastgelegde vorm en semantiek behoudt. Daarom moet het vergezeld gaan van voorbeelden die relevante regels uitdrukken, niet alleen lege schema's.

Wat deel uitmaakt van het contract van een API

Het contract omvat elk gedrag dat een afnemer kan waarnemen en waarvan die afhankelijk is. Het beperken tot de JSON van een succesvol antwoord laat de meest voorkomende breuken buiten beschouwing. Voor elke operatie is het raadzaam minimaal de volgende elementen af te spreken.

  • Verzoek: methode, route, queryparameters, headers, body, verplichte velden, formaten en limieten.
  • Antwoord: HTTP-code, relevante headers, structuur, typen, optionele velden, velden die nullwaarden toestaan en formaten voor datum, valuta of identifiers.
  • Fouten: statuscodes, foutbody, stabiele functionele code en de voorwaarden die deze veroorzaken. Tekst voor mensen kan wijzigen; een code zoals validation_failed is geschikter voor automatisering.
  • Paginering en filtering: betekenis van limit, cursor of pagina, stabiliteit van de volgorde, weergave van de volgende cursor en behandeling van lege verzamelingen.
  • Authenticatie en autorisatie: toegestaan mechanisme, vereiste headers, scopes of rechten, en het verschil tussen ongeldige inloggegevens, ontbrekende inloggegevens en geweigerde toegang.
  • Events en webhooks: eventnaam, versie of schema van de payload, handtekening, opnieuw proberen, eventidentifier, niet-gegarandeerde volgorde en verwachtingen rond idempotentie.

De verplichting van een eigenschap en het toestaan van null zijn onafhankelijke regels. Een veld kan verplicht zijn en null toestaan, optioneel zijn en het niet toestaan wanneer het voorkomt, of optioneel zijn en het toestaan als het aanwezig is. Ook een weggelaten eigenschap, een aanwezige eigenschap met null en een eigenschap met een lege string zijn verschillende toestanden. Als de afnemer deze anders interpreteert, moet het contract dit uitdrukken en testen.

Kleine wijzigingen die afnemers kunnen breken

Een wijziging kan vanuit het perspectief van de aanbieder onschuldig lijken en incompatibel zijn voor een gegenereerde client, een strikte validator of bedrijfslogica. Een integer wijzigen in een string, bijvoorbeeld 42 naar "42", breekt vergelijkingen en schema's. Een veld optioneel maken bepaalt op zichzelf niet of het null accepteert: de eerste regel bepaalt of de eigenschap aanwezig moet zijn, terwijl de tweede de geldige waarden bepaalt wanneer deze is opgenomen.

Andere risicovolle wijzigingen zijn 200 retourneren waar eerder 201 werd geretourneerd, een lege lijst vervangen door null, de precisie van een decimaal wijzigen, een foutcode hernoemen of de pagineringsvolgorde zonder waarschuwing wijzigen. Een veld toevoegen is doorgaans compatibel voor tolerante lezers, maar niet als een afnemer een gesloten schema valideert of handtekeningen berekent over de volledige body.

Compatibiliteit hangt af van de werkelijke afspraak, niet van een geïsoleerde regel. Het is raadzaam elke wijziging te classificeren volgens bekende afnemers, verklaarde tolerantie en de kritikaliteit van de werkstroom. Als die informatie niet bekend is, moet dit als een risico worden behandeld en niet als een gunstige aanname.

Kiezen tussen specificatie, afnemercontracten of beide

Een interfacespecificatie, bijvoorbeeld een OpenAPI-beschrijving, werkt goed als gedeelde bron voor routes, operaties, parameters, schema's en antwoorden. Deze kan in de pijplijn worden gevalideerd om incompatibele wijzigingen ten opzichte van een referentieversie te detecteren. Dit is nuttig wanneer er veel afnemers zijn of clients en documentatie uit dezelfde definitie worden gegenereerd.

Een schema legt echter niet altijd vast wat voor elke afnemer van belang is: combinaties van filters, een specifieke fout bij een bedrijfsvoorwaarde of een afhankelijkheid van een voorbeeldwaarde. Contracten aangestuurd door afnemers beschrijven concrete interacties die elke afnemer nodig heeft. De aanbieder verifieert ze tegen zijn implementatie.

Beide niveaus gebruiken is doorgaans redelijk: de specificatie beheerst het algemene oppervlak en afnemercontracten dekken hoogwaardige werkstromen of semantiek die moeilijk tot een schema te reduceren is. Voor elk artefact moet een duidelijke verantwoordelijkheid bestaan. Als een specificatie niet wordt bijgewerkt wanneer de code verandert, is zij niet langer de gezaghebbende bron maar fictieve documentatie.

Representatieve voorbeelden en randgevallen

Een contractvoorbeeld moet realistische gegevens qua structuur bevatten, geen productiegegevens. Neem voor een orderresource een geval met items op, een leeg geval als dat geldig is, identifiers met het afgesproken formaat en volledige datums met tijdzone wanneer dat de conventie is. Voeg gevallen toe voor geweigerde autorisatie, mislukte validatie, niet-bestaande resource en de laatste pagina.

Vermijd het vastleggen van irrelevante details die terecht veranderen, zoals een willekeurige identifier, de huidige tijd of de volgorde van JSON-eigenschappen. Gebruik nauwkeurige controles voor het stabiele en expliciete tolerantie voor het variabele. Elk voorbeeld moet aan een bekende behoefte voldoen; een enorme verzameling verzonnen antwoorden verhoogt het onderhoud zonder het vertrouwen te vergroten.

Geleidelijke implementatie in een bestaande PHP-API

Het is niet nodig om de volledige API te modelleren voordat u waarde verkrijgt. Begin met een inventaris van afnemers: interne applicaties, B2B-clients, batchprocessen, mobiele applicaties, automatiseringen en webhookontvangers. Registreer eigenaar, contactkanaal, gebruikte bewerking, kritikaliteit en vermogen om updates door te voeren.

Geef vervolgens prioriteit aan endpoints die resources creëren of wijzigen, gebruikers authenticeren, financiële processen voeden of automatiseringen activeren. Stel een uitgangssituatie vast van het huidige gedrag met een beoordeelde specificatie en tests tegen een reproduceerbare instantie van de API. In PHP moet de test de werkelijke HTTP-laag van de applicatie testen, niet rechtstreeks een serviceklasse aanroepen: het contract omvat routing, middleware, serialisatie en afhandeling van uitzonderingen.

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

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

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

Het voorgaande voorbeeld is alleen nuttig als het vergezeld gaat van regels: welke velden verplicht zijn, of id altijd een string is, welke fouten een ongeldige SKU retourneert en of de initiële status gegarandeerd is. Deze regels moeten in controles worden omgezet.

Validatie in continuous integration en vóór de uitrol

De pijplijn moet vóór het samenvoegen falen als de implementatie goedgekeurde contracten schendt. Een praktische werkstroom omvat het uitvoeren van unit tests, het starten van gecontroleerde afhankelijkheden, het starten van de PHP-API met een testconfiguratie en het valideren van de specificatie, contracten van de aanbieder en contracten van representatieve afnemers. De tests moeten geïsoleerde en deterministische gegevens gebruiken, zodat een fout reproduceerbaar is.

Vergelijk in een wijzigingsverzoek bovendien de voorgestelde specificatie met de gepubliceerde versie om het verwijderen van routes, strengere vereisten voor aanwezigheid, wijzigingen in het toestaan van null, typewijzigingen en verwijderde antwoorden te signaleren. De diagnose moet operatie, interactie en geschonden regel aangeven; een eenvoudige schemafout maakt anders uitgebreid onderzoek noodzakelijk.

Voer vóór de uitrol dezelfde suite uit op het artefact dat zal worden gepubliceerd, niet op een andere build. Monitor na de uitrol foutcodes, door clients gerapporteerde deserialisatiefouten, versiegebruik en verkeer naar uitgefaseerde routes. Validatie vooraf vermindert risico; zij vervangt niet het bevestigen van gedrag onder werkelijk verkeer.

Compatibiliteit, uitfasering en veilige verwijdering

Compatibiliteit, uitfasering en veilige verwijdering — guía visual de DedicatedPHP

Wanneer een wijziging niet compatibel is, kies dan voor een expliciete overgang. U kunt een nieuw veld toevoegen terwijl u het oude behoudt, een nieuwe operatie of versie invoeren, en een uitfaseringsdatum communiceren die door gebruikssignalen wordt ondersteund. Uitfasering is een operationele periode met eigenaren, communicatie en meting; niet slechts een opmerking in de documentatie.

Verwijder geen gedrag omdat een termijn is verstreken als u niet kunt vaststellen welke afnemers nog afhankelijk zijn van dat gedrag of als de werkstroom kritiek is. Stel waar mogelijk gecontroleerde waarschuwingen en meetgegevens beschikbaar om oud gebruik te lokaliseren zonder het antwoord te wijzigen. Met een gefaseerde uitrol van een nieuwe versie kunt u fouten observeren en contracten corrigeren voordat de uitrol verder wordt opgeschaald.

De meest voorkomende fouten zijn alleen succesvolle antwoorden testen, menselijke berichten modelleren in plaats van foutcodes, aannemen dat alle clients nieuwe velden negeren en echte of representatieve afnemers niet betrekken. Contracttests leveren waarde wanneer ze afspraken weerspiegelen die door beide partijen worden onderhouden en als een normale voorwaarde voor oplevering worden uitgevoerd.

Wil je deze ideeën toepassen op je project?Laten we uw PHP-platform bespreken.
Bekijk gerelateerde service