Naar hoofdinhoud
Odoo · Engineering-postmortem

De tests waren groen en de connector werkte niet: 32 fouten die de suite niet zag

Een groene suite bewijst dat je code zich gedraagt zoals je mock. Ze bewijst niet dat de externe API nog is wat je dacht. Hier is het geval, met cijfers, en de tests die het wel hadden ontdekt.

Apps-scherm van Odoo met de geïnstalleerde en actieve modules in een echte backend

Als je Odoo-tests slagen en de connector in productie faalt, is de reden bijna altijd dezelfde: de suite test niet de API, maar je mock van de API. Die mock heb je zelf geschreven, op basis van de documentatie van die dag, en sindsdien is hij niet veranderd. De API wel. Een .get("shippingDate") waarvan het veld nu anders heet, gooit geen enkele exceptie: het geeft voor altijd None terug, en de test die het afdekt blijft groen omdat de mock ook shippingDate zegt.

Kernidee: een test tegen een mock bewijst interne consistentie, geen externe conformiteit. Slechts één van de twee waarschuwt je wanneer de andere kant verandert.

Het geval: 236 van 236 groen in Odoo 19, 18 en 17

We auditeerden een eigen marketplace-connector voordat we hem voor een echte verkoper zetten. De suite gaf 236 van 236 groen, en niet lokaal: op echte Odoo.sh-builds van de drie series die we ondersteunen.

Hij werkte niet. De audit vond 32 echte fouten onder het groen: 20 van contract en 12 van versie. Geen van de 236 tests raakte ze, omdat geen enkele buiten de module keek.

Wat er echt kapot was: 20 contractfouten en 12 versiefouten

Een contractfout is een verschil tussen wat de module verstuurt of leest en wat de API declareert. Er kwamen drie varianten uit:

  • Een verplichte queryparameter die niet werd meegestuurd. De API antwoordt 400 en de fout is luidruchtig… in productie.
  • Een parameter die de specificatie bij geen enkele operatie declareert. Dat geeft geen fout: hij wordt genegeerd. Het antwoord komt ongefilterd binnen en het lijkt te werken. Dit is de gevaarlijke.
  • Een responsveld dat onder de verkeerde naam wordt gelezen. .get() geeft None terug en er loopt nooit iets vast.

Vier van die twintig kwamen niet uit het lezen van de specificatie: ze kwamen uit het aanroepen. De paginatie-header Link kwam relatief binnen en zo was het geen geldige URL: vanaf pagina één werd geen enkele bestelling geïmporteerd. En een 403 wegens overschreden quotum kwam niet in het gedocumenteerde foutformaat, dus werd het gelezen als een rechtenfout: de connector markeerde het account als niet geautoriseerd en stopte de hele synchronisatie.

Een versiefout is iets anders: dezelfde code gedraagt zich anders per Odoo-serie. Deze kwamen uit de module zelf:

  • models.Constraint(...) bestaat in Odoo 19. In 18 en 17 is het geen constraint: de klasse laadt, er is geen enkele fout, en de garantie van uniciteit is verdwenen.
  • res.groups spelt zijn leden users vóór Odoo 19 en user_ids vanaf 19. Als je de verkeerde kant kiest, installeert de module niet.
  • Een ir.cron zonder numbercall krijgt in Odoo 17 de standaardwaarde 1: de cron draait één keer en zet zichzelf uit. Niemand ziet een fout. Hij stopt gewoon met synchroniseren op dinsdag.
  • Odoo 17 schrijft <tree> waar 18 en 19 <list> schrijven, en weigert een weergave waarvan invisible een veld leest dat de weergave niet toont: 18 en 19 tolereren dat.

Let op het patroon: de helft van die lijst geeft geen fout. Het geeft stilte. En stilte wordt door geen enkele assertie gevangen die er niet naar zoekt.

Waarom de suite het niet zag: de anatomie van een test die zichzelf test

Een typische connectortest heeft drie onderdelen: een mock die een JSON teruggeeft, de aanroep van de methode en een assertie op het resultaat. Als je die JSON schreef met je eigen parser voor ogen in plaats van de specificatie van de leverancier, is de test een spiegel: hij slaagt altijd, en de dag dat de API verandert ook.

Er is een tweede vorm van leeg groen, en die is specifiek voor Odoo: de suite werd niet eens uitgevoerd. In de runner van Odoo.sh draaien de post_install-tests alleen in een echte installatie: een -u, of een -i op een al geïnstalleerde module, voert nul tests uit en eindigt met code 0. En de --test-tags "/modulo:Clase.metodo"-selectors matchen daar met niets; alleen /modulo werkt. Een slecht geschreven pipeline meldt "geen fouten" omdat er geen tests waren.

We leerden het met een ongemakkelijk feit, en een eigen feit: een van onze eigen conformiteitscontroles meldde nul bevindingen door een eigen bug. Hij loste alleen components/schemas op, terwijl de 200-antwoorden van die API naar components/responses verwijzen, waardoor hij bijna alle endpoints oversloeg. "Nul bevindingen" en "nul gecontroleerde endpoints" schrijf je hetzelfde.

Het tweede geval, erger: een dubbele terugbetaling met een groene suite

Tweede audit, een andere connector, van een marketplace-platform. 163 van 163 groen, ook in de drie series en op echte builds. Eronder: het aanroeplogboek had nooit één rij geschreven, de incrementele synchronisatie vroeg het verkeerde datumvenster op, en een terugbetaling kon twee keer worden verstuurd.

Die over geld is degene die een mock niet kan reproduceren. De HTTP-client probeerde opnieuw bij 5xx en bij timeout, en voor elke methode: ook voor de PUT die een terugbetaling verstuurt. Een timeout zegt niet dat het verzoek niet aankwam, het zegt dat je niet weet of het aankwam; daar een niet-idempotente schrijfactie opnieuw proberen is twee keer terugbetalen. En bij omleidingen werd de methode herschreven: een PUT van een terugbetaling kon eindigen in een GET zonder credentials dat 200 teruggaf en als succes werd genoteerd. Geen enkele test zag het, en dat was geen slordigheid: een mock antwoordt altijd meteen, zonder timeouts, zonder 502 en zonder omleidingen. De tak die schade doet, is degene die de mock nooit uitvoert.

Die van de datums past in één woord: de incrementele synchronisaties vroegen wat sinds de laatste ronde aangemaakt was in plaats van wat gewijzigd was. Een bestelling die 's ochtends werd aangemaakt en 's middags verzonden, kwam niet meer in het venster en haar status in Odoo bleef bevroren.

Die van het logboek is de stilste van de drie. De helper die het logmodel teruggeeft, geeft een recordset terug, en als alles goed gaat is die recordset leeg; in Odoo is een lege recordset onwaar. De controle was if not log_model: return False, dus die klopte altijd en alle paden van "dit loggen we" waren een no-op: geen exceptie, geen trace, geen rij. Het bewijs dat het vanaf de eerste dag zo was, kwam niet uit een test, maar uit het in productie bekijken van de teller van de sequentie van die tabel en die op NULL vinden na maanden cronjobs.

De drie soorten tests die het wel hadden gevangen

1. Contract tegen de echte specificatie, niet tegen je mock

Je hebt geen broker of infrastructuur nodig: het volstaat de OpenAPI van de leverancier in de repository te hebben en die statisch tegen je client te vergelijken. Drie poorten die in seconden draaien en met code 1 eindigen als ze iets vinden:

  • Queryparameters: wat elke operatie declareert tegenover wat elke wrapper verstuurt. Vangt de ontbrekende verplichte en de naam die niemand declareert.
  • Schrijfbodies: de sleutels van het eerste niveau van de payload tegen het schema van het verzoek.
  • Responsparsers: elke veldnaam die de code leest tegen het schema van het antwoord.

De run op die connector: 87 wrappers gemapt tegen 88 operaties van de specificatie, 20 schrijfroutes met JSON-body, 38 parsers op 38 verschillende operaties (5 multi-endpointfuncties niet analyseerbaar), 0 bevindingen. Die nul betekent iets omdat de noemer ernaast is afgedrukt.

En als de leverancier een sandbox biedt, één echte aanroep per endpointfamilie: het is de enige test die het verschil laat zien tussen "de specificatie zegt dit" en "de server doet dit".

2. Echte installatie, geen import (en daarna uitproberen)

Dat een module importeert zegt niets; dat hij compileert ook niet. De test is hem installeren: -i op een schone database en -i op een database die al de vorige versie had. Het zijn twee verschillende tests, en de tweede is degene die vastloopt op de noupdate-gegevens en de migraties. We werkten het uit in een module die in Odoo 19 compileert en toch niet installeert.

En na het installeren, hem uitproberen. We lieten 47 apps uit onze catalogus door schone installaties van Odoo 17 en 18 Community gaan (installeren en dan get_views, search_read en onchange aanroepen): 61 van 94 versieruns kwamen schoon uit. Van de resterende 33 kwamen de meeste niet eens tot installatie, vier waren artefacten van het testen van elke app zonder haar zustermodule, en twee installeerden prima en liepen vast bij het uitproberen: een Expected singleton in een compute van product.template en een model dat Enterprise veronderstelde in een Community-database.

3. Verificatie in de drie versies, een voor een

Een module die naar 19, 18 en 17 gaat, zijn drie modules die op elkaar lijken. De lijst met valkuilen hierboven hebben we als statische controle die in een seconde draait op de gegenereerde port, voordat we een build verspillen: elk item was een echte fout en de helft faalde niet luidruchtig. Het is ook de kern van het argument wanneer iemand een native connector vergelijkt met een tussenlaag: het contractoppervlak verdwijnt niet, het verandert alleen van eigenaar. We behandelen het in Odoo 19 versus middleware-connectoren.

Wat je maandag verandert

  • Bewaar de specificatie van de leverancier in de repository, geversioneerd naast de code die haar gebruikt. Haar diff is je alarm.
  • Geef elke fixture een datum en contractversie. Een mock die niet zegt waartegen hij is geschreven, is al verlopen en je weet het niet.
  • Schrijf een statische vergelijker tussen je client en die specificatie. Tweehonderd regels Python die met een code ongelijk aan nul eindigen.
  • Laat de pipeline falen als het aantal uitgevoerde tests nul is, of als het daalt ten opzichte van de vorige run. "0 fouten" betekent niets; "0 fouten van 236" wel.
  • Installeer echt in de CI (schone database en database met de vorige versie) en probeer daarna uit: open de weergaven en lees de records via de ORM. En als je meerdere Odoo-series onderhoudt, draai de suite in alle.

En de verandering die echt moeite kost, en die niet technisch is: "de suite is groen" beantwoordt niet "werkt het?", maar "doet mijn code nog wat ik dacht?". Het zijn twee vragen, en ze door elkaar halen kostte ons 32 fouten in de ene connector en een latente dubbele terugbetaling in de andere.

Als je connectoren voor klanten onderhoudt en een tweede lezing wilt over wat je suite niet test, zo werken we met adviesbureaus en technische teams en met andere Odoo-partners.

Nuttige links binnen FlexigoTech

Werken met Odoo-partnersHoe we ons integreren met teams die al hun eigen ontwikkelcyclus hebbenOdoo-engineering voor adviesbureausTechnische capaciteit voor adviesbureaus die onderhouden connectoren nodig hebbenOdoo 19 versus middleware-connectorenWaar het contractoppervlak zit wanneer je een tussenlaag plaatstMirakl-connector voor OdooHoe we de synchronisatie van bestellingen, retouren en afrekeningen aanpakken

Veelgestelde vragen

Waarom slagen mijn Odoo-tests en faalt de connector in productie?

Omdat de suite je mock test, niet de API. De mock schreef je tegen de documentatie van die dag en hij is sindsdien niet veranderd; de API wel. Een veld dat onder de verkeerde naam wordt gelezen, geeft None terug zonder exceptie, dus geen enkele assertie merkt het. De enige manier om het te ontdekken is je client te vergelijken met de officiële specificatie van de leverancier, of minstens één echte aanroep naar de sandbox te doen.

Wat is contract testing toegepast op een Odoo-connector?

In de praktische versie heeft het geen broker of infrastructuur nodig: je bewaart de OpenAPI van de leverancier in de repository en vergelijkt er statisch tegen de parameters die je verstuurt, de sleutels van de schrijfbodies en de veldnamen die elke parser leest. Drie scripts die met een code ongelijk aan nul eindigen. Ze dekken de meeste contractfouten af voordat Odoo start.

Hoe weet ik of mijn mock verouderd is ten opzichte van de API?

Drie signalen: de fixture geeft niet aan tegen welke versie van de specificatie hij is geschreven; de specificatie van de leverancier is niet geversioneerd in de repository, dus je kunt haar diff niet zien; en geen enkele test raakt ooit het netwerk. Als alle drie gelden, is je mock al verouderd ook al is de suite groen.

Volstaat het de module in Odoo 19 te testen als ik hem ook in 18 en 17 publiceer?

Nee. Er zijn constructies die in 19 bestaan en in 18 of 17 niet falen: ze worden genegeerd. models.Constraint is in 18 of 17 geen constraint en geeft geen fout, dus je verliest de uniciteit stilletjes. Een ir.cron zonder numbercall in 17 zet zich uit na de eerste uitvoering. Toen we 47 apps door schone installaties van 17 en 18 Community haalden, kwamen 61 van 94 runs schoon uit: een derde niet.

Is een installatietest hetzelfde als een importtest?

Nee, en ze verwarren is een van de duurste fouten. De module importeren bewijst alleen dat de Python geldig is. Hem installeren laadt de XML, valideert de weergaven, past de beveiliging toe en voert de migraties uit. En zelfs geïnstalleerd moet je hem uitproberen: in onze eigen catalogus waren er apps die prima installeerden en vastliepen bij het openen van een weergave of het lezen van een record via de ORM.

Wat we hieraan doen

MaatwerkintegratiesWat het doet, schermafbeeldingen, versies en prijs.

Heb je een connector met een groene suite en twijfel je of hij werkt?

We controleren het contractoppervlak tegen de echte specificatie van de leverancier en de installatie in de Odoo-series die je ondersteunt. Schrijf naar comercial@flexigobe.com of bel +34 616 809 504.

Praat met een engineer