Zum Hauptinhalt springen
Odoo · Engineering-Postmortem

Die Tests waren grün und der Konnektor funktionierte nicht: 32 Fehler, die die Suite nicht sah

Eine grüne Suite beweist, dass Ihr Code sich so verhält wie Ihr Mock. Sie beweist nicht, dass die Remote-API noch die ist, für die Sie sie halten. Hier der Fall mit Zahlen und die Tests, die ihn gefunden hätten.

Odoo-Apps-Ansicht mit den installierten und aktiven Modulen auf einem echten Backend

Wenn Ihre Odoo-Tests bestehen und der Konnektor in der Produktion scheitert, liegt es fast immer am selben Grund: Die Suite testet nicht die API, sie testet Ihren Mock der API. Den Mock haben Sie selbst geschrieben, gegen die Dokumentation von damals, und seitdem hat er sich nicht verändert. Die API schon. Ein .get("shippingDate"), dessen Feld jetzt anders heißt, wirft keine Exception: Es liefert für immer None, und der Test dafür bleibt grün, weil auch der Mock shippingDate sagt.

Kernaussage: Ein Test gegen einen Mock beweist interne Konsistenz, keine externe Konformität. Nur eines von beiden warnt Sie, wenn sich die Gegenseite ändert.

Der Fall: 236 von 236 grün in Odoo 19, 18 und 17

Wir haben einen eigenen Marketplace-Konnektor geprüft, bevor wir ihn einem echten Händler vorgesetzt haben. Die Suite zeigte 236 von 236 grün, und zwar nicht lokal, sondern auf echten Odoo.sh-Builds in den drei Serien, die wir unterstützen.

Er funktionierte nicht. Das Audit fand unter dem Grün 32 echte Fehler: 20 Vertragsfehler und 12 Versionsfehler. Keiner der 236 Tests berührte sie, weil keiner über das Modul hinausschaute.

Was wirklich kaputt war: 20 Vertragsfehler und 12 Versionsfehler

Ein Vertragsfehler ist eine Abweichung zwischen dem, was das Modul sendet oder liest, und dem, was die API deklariert. Es gab drei Varianten:

  • Ein erforderlicher Abfrageparameter, der nicht gesendet wurde. Die API antwortet mit 400, und der Fehler ist laut… in der Produktion.
  • Ein Parameter, den die Spezifikation in keiner Operation deklariert. Das ergibt keinen Fehler: Er wird ignoriert. Die Antwort kommt ungefiltert an und wirkt, als hätte es funktioniert. Dieser ist der gefährliche.
  • Ein Antwortfeld, das mit dem falschen Namen gelesen wird. .get() liefert None, und nie geht etwas kaputt.

Vier dieser zwanzig stammen nicht aus dem Lesen der Spezifikation, sondern aus echten Aufrufen. Der Paginierungs-Header Link kam relativ an und war so keine gültige URL: Ab Seite eins wurde keine einzige Bestellung importiert. Und ein 403 wegen überschrittenen Kontingents kam nicht im dokumentierten Fehlerformat und wurde deshalb als Berechtigungsfehler gelesen: Der Konnektor markierte das Konto als nicht autorisiert und stoppte die gesamte Synchronisierung.

Ein Versionsfehler ist etwas anderes: Derselbe Code verhält sich je nach Odoo-Serie anders. Diese stammen aus dem Modul selbst:

  • models.Constraint(...) gibt es in Odoo 19. In 18 und 17 ist es keine Constraint: Die Klasse lädt, es gibt keinen Fehler, und die Eindeutigkeitsgarantie ist verschwunden.
  • res.groups schreibt seine Mitglieder vor Odoo 19 users und ab 19 user_ids. Wer die Seite verwechselt, dessen Modul lässt sich nicht installieren.
  • Ein ir.cron ohne numbercall nimmt in Odoo 17 den Standardwert 1 an: Der Cron läuft einmal und deaktiviert sich selbst. Niemand sieht einen Fehler. Er hört am Dienstag einfach auf zu synchronisieren.
  • Odoo 17 schreibt <tree>, wo 18 und 19 <list> schreiben, und lehnt eine Ansicht ab, deren invisible ein Feld liest, das die Ansicht nicht anzeigt: 18 und 19 tolerieren das.

Achten Sie auf das Muster: Die Hälfte dieser Liste erzeugt keinen Fehler. Sie erzeugt Stille. Und Stille fängt keine Assertion, die nicht danach sucht.

Warum die Suite es nicht sah: die Anatomie eines Tests, der sich selbst testet

Ein typischer Konnektor-Test hat drei Teile: einen Mock, der ein JSON zurückgibt, den Methodenaufruf und eine Assertion auf das Ergebnis. Wenn Sie dieses JSON mit Blick auf Ihren eigenen Parser statt auf die Spezifikation des Anbieters geschrieben haben, ist der Test ein Spiegel: Er besteht immer, und am Tag, an dem sich die API ändert, ebenfalls.

Es gibt eine zweite Form von leerem Grün, und sie ist Odoo-spezifisch: Die Suite wurde gar nicht ausgeführt. Im Runner von Odoo.sh laufen die post_install-Tests nur bei einer echten Installation: Ein -u oder ein -i auf einem bereits installierten Modul führt null Tests aus und endet mit Exit-Code 0. Und die --test-tags "/modulo:Clase.metodo"-Selektoren passen dort auf nichts; nur /modulo funktioniert. Eine falsch geschriebene Pipeline meldet «keine Fehler», weil es keine Tests gab.

Gelernt haben wir das an einer unbequemen, eigenen Tatsache: Einer unserer eigenen Konformitätsprüfer meldete wegen eines eigenen Bugs null Befunde. Er löste nur components/schemas auf, während die 200-Antworten jener API auf components/responses zeigen, und übersprang so fast alle Endpunkte. «Null Befunde» und «null geprüfte Endpunkte» schreiben sich gleich.

Der zweite Fall, schlimmer: eine doppelte Rückerstattung bei grüner Suite

Zweites Audit, anderer Konnektor, für eine Marketplace-Plattform. 163 von 163 grün, ebenfalls in den drei Serien und auf echten Builds. Darunter: Das Aufrufprotokoll hatte nie eine Zeile geschrieben, die inkrementelle Synchronisierung fragte das falsche Datumsfenster ab, und eine Rückerstattung konnte zweimal gesendet werden.

Der Fall mit dem Geld ist der, den ein Mock nicht reproduzieren kann. Der HTTP-Client wiederholte bei 5xx und bei Timeout, und zwar für jede Methode: auch für das PUT, das eine Rückerstattung sendet. Ein Timeout sagt nicht, dass die Anfrage nicht angekommen ist, sondern dass Sie nicht wissen, ob sie ankam; einen nicht idempotenten Schreibvorgang dort zu wiederholen heißt, doppelt zu erstatten. Und bei Weiterleitungen wurde die Methode umgeschrieben: Ein PUT einer Rückerstattung konnte als GET ohne Zugangsdaten enden, der 200 zurückgab und als Erfolg verbucht wurde. Kein Test sah es, und das war kein Versehen: Ein Mock antwortet immer sofort, ohne Timeouts, ohne 502 und ohne Weiterleitungen. Der Zweig, der Schaden anrichtet, ist der, den der Mock nie ausführt.

Der mit den Daten passt in ein Wort: Die inkrementellen Synchronisierungen fragten seit dem letzten Durchlauf nach dem Erstellten statt nach dem Geänderten. Eine am Vormittag erstellte und am Nachmittag versandte Bestellung kam nie wieder ins Fenster, und ihr Status in Odoo blieb eingefroren.

Der mit dem Log ist der stillste der drei. Der Helfer, der das Log-Modell zurückgibt, liefert ein Recordset, und wenn alles gut läuft, ist dieses Recordset leer; in Odoo ist ein leeres Recordset falsch. Die Prüfung lautete if not log_model: return False, traf also immer zu, und alle Pfade mit «das protokollieren wir» waren ein No-op: keine Exception, keine Spur, keine Zeile. Der Beweis, dass es von Anfang an so war, kam aus keinem Test, sondern aus einem Blick in die Produktion auf den Sequenzzähler dieser Tabelle, der nach Monaten voller Crons auf NULL stand.

Die drei Testarten, die es gefunden hätten

1. Vertrag gegen die echte Spezifikation, nicht gegen Ihren Mock

Es braucht keinen Broker und keine Infrastruktur: Es genügt, das OpenAPI des Anbieters im Repository zu haben und es statisch mit Ihrem Client zu vergleichen. Drei Gates, die in Sekunden laufen und mit Code 1 enden, wenn sie etwas finden:

  • Abfrageparameter: was jede Operation deklariert gegenüber dem, was jeder Wrapper sendet. Fängt den fehlenden erforderlichen und den Namen, den niemand deklariert.
  • Schreib-Bodys: die Schlüssel der ersten Ebene des Payloads gegen das Schema der Anfrage.
  • Antwort-Parser: jeder Feldname, den der Code liest, gegen das Schema der Antwort.

Der Lauf über diesen Konnektor: 87 Wrapper gegen 88 Operationen der Spezifikation abgebildet, 20 Schreibpfade mit JSON-Body, 38 Parser über 38 verschiedene Operationen (5 Multi-Endpunkt-Funktionen nicht analysierbar), 0 Befunde. Diese Null bedeutet etwas, weil der Nenner direkt daneben steht.

Und wenn der Anbieter eine Sandbox bietet, ein echter Aufruf pro Endpunkt-Familie: Das ist der einzige Test, der «die Spezifikation sagt das» von «der Server tut das» unterscheidet.

2. Echte Installation, nicht Import (und danach ausführen)

Dass ein Modul importiert, sagt nichts; dass es kompiliert, auch nicht. Der Test ist die Installation: -i auf einer sauberen Datenbank und -i auf einer Datenbank, die schon die Vorgängerversion hatte. Das sind zwei verschiedene Tests, und der zweite scheitert an den noupdate-Daten und den Migrationen. Wir haben das in einem Modul, das in Odoo 19 kompiliert und sich trotzdem nicht installieren lässt, ausführlich behandelt.

Und nach der Installation ausführen. Wir haben 47 Apps aus unserem Katalog durch saubere Installationen von Odoo 17 und 18 Community geschickt (installieren und dann get_views, search_read und onchange aufrufen): 61 von 94 Versionsläufen blieben sauber. Von den übrigen 33 ließen sich die meisten gar nicht installieren, vier waren Artefakte davon, jede App ohne ihr Schwestermodul zu testen, und zwei installierten einwandfrei und scheiterten beim Ausführen: ein Expected singleton in einem Compute von product.template und ein Modell, das auf einer Community-Datenbank Enterprise voraussetzte.

3. Verifikation in den drei Versionen, einzeln

Ein Modul, das für 19, 18 und 17 läuft, sind drei Module, die sich ähneln. Die Liste der Fallstricke von oben haben wir als statische Prüfung, die in einer Sekunde über den erzeugten Port läuft, bevor ein Build verbraucht wird: Jeder Eintrag war ein echter Fehler, und die Hälfte scheiterte nicht laut. Das ist auch der Kern des Arguments, wenn jemand nativen Konnektor gegen Zwischenschicht vergleicht: Die Vertragsfläche verschwindet nicht, sie wechselt nur den Besitzer. Wir behandeln das in Odoo 19 gegenüber Middleware-Konnektoren.

Was Sie am Montag ändern sollten

  • Legen Sie die Spezifikation des Anbieters im Repository ab, versioniert zusammen mit dem Code, der sie nutzt. Ihr Diff ist Ihr Alarm.
  • Versehen Sie jede Fixture mit Datum und Vertragsversion. Ein Mock, der nicht sagt, wogegen er geschrieben wurde, ist bereits veraltet, und Sie wissen es nicht.
  • Schreiben Sie einen statischen Vergleicher zwischen Ihrem Client und dieser Spezifikation. Zweihundert Zeilen Python, die mit einem Code ungleich null enden.
  • Lassen Sie die Pipeline scheitern, wenn die Zahl der ausgeführten Tests null ist oder gegenüber dem vorigen Lauf sinkt. «0 Fehler» bedeutet nichts; «0 Fehler von 236» schon.
  • Installieren Sie in der CI wirklich (saubere Datenbank und Datenbank mit der Vorgängerversion) und führen Sie danach aus: Öffnen Sie die Ansichten und lesen Sie die Datensätze über das ORM. Und wenn Sie mehrere Odoo-Serien pflegen, lassen Sie die Suite in allen laufen.

Und die Änderung, die wirklich etwas kostet und nicht technisch ist: «Die Suite ist grün» beantwortet nicht «Funktioniert es?», sondern «Tut mein Code noch das, was ich dachte?». Das sind zwei Fragen, und sie zu verwechseln hat uns 32 Fehler in einem Konnektor und eine latente doppelte Rückerstattung in einem anderen gekostet.

Wenn Sie Konnektoren für Kunden pflegen und eine zweite Einschätzung dazu möchten, was Ihre Suite nicht testet, so arbeiten wir mit Beratungshäusern und technischen Teams und mit anderen Odoo-Partnern.

Nützliche Links bei FlexigoTech

Zusammenarbeit mit Odoo-PartnernWie wir uns in Teams einfügen, die bereits ihren eigenen Entwicklungszyklus habenOdoo-Engineering für BeratungshäuserTechnische Kapazität für Beratungshäuser, die gepflegte Konnektoren brauchenOdoo 19 gegenüber Middleware-KonnektorenWo die Vertragsfläche liegt, wenn Sie eine Zwischenschicht einziehenMirakl-Konnektor für OdooWie wir die Synchronisierung von Bestellungen, Retouren und Abrechnungen angehen

Häufig gestellte Fragen

Warum bestehen meine Odoo-Tests, während der Konnektor in der Produktion scheitert?

Weil die Suite Ihren Mock testet, nicht die API. Den Mock haben Sie gegen die Dokumentation von damals geschrieben, und er hat sich seitdem nicht verändert; die API schon. Ein Feld, das mit dem falschen Namen gelesen wird, liefert None ohne Exception, sodass keine Assertion etwas bemerkt. Erkennen lässt sich das nur, indem Sie Ihren Client mit der Spezifikation des Anbieters vergleichen oder mindestens einen echten Aufruf an die Sandbox machen.

Was ist Contract Testing bei einem Odoo-Konnektor?

In der praktischen Variante braucht es keinen Broker und keine Infrastruktur: Sie legen das OpenAPI des Anbieters im Repository ab und vergleichen statisch dagegen die Parameter, die Sie senden, die Schlüssel der Schreib-Bodys und die Feldnamen, die jeder Parser liest. Drei Skripte, die mit einem Code ungleich null enden. Sie decken die meisten Vertragsfehler ab, bevor Odoo startet.

Woran erkenne ich, ob mein Mock gegenüber der API veraltet ist?

Drei Anzeichen: Die Fixture gibt nicht an, gegen welche Version der Spezifikation sie geschrieben wurde; die Spezifikation des Anbieters ist nicht im Repository versioniert, sodass Sie ihren Diff nicht sehen können; und kein Test berührt jemals das Netzwerk. Treffen alle drei zu, ist Ihr Mock bereits veraltet, auch wenn die Suite grün ist.

Genügt es, das Modul in Odoo 19 zu testen, wenn ich es auch für 18 und 17 veröffentliche?

Nein. Es gibt Konstrukte, die in 19 existieren und in 18 oder 17 nicht scheitern: Sie werden ignoriert. models.Constraint ist in 18 oder 17 keine Constraint und erzeugt keinen Fehler, sodass Sie die Eindeutigkeit stillschweigend verlieren. Ein ir.cron ohne numbercall schaltet sich in 17 nach der ersten Ausführung selbst ab. Als wir 47 Apps durch saubere Installationen von 17 und 18 Community schickten, blieben 61 von 94 Läufen sauber: ein Drittel nicht.

Ist ein Installationstest dasselbe wie ein Importtest?

Nein, und sie zu verwechseln gehört zu den teuersten Fehlern. Der Import des Moduls beweist nur, dass das Python gültig ist. Die Installation lädt das XML, validiert die Ansichten, wendet die Sicherheit an und führt die Migrationen aus. Und selbst installiert muss es ausgeführt werden: In unserem eigenen Katalog gab es Apps, die einwandfrei installierten und beim Öffnen einer Ansicht oder beim Lesen eines Datensatzes über das ORM scheiterten.

Was wir dazu tun

Individuelle IntegrationenWas sie leistet, Screenshots, Versionen und Preis.

Haben Sie einen Konnektor mit grüner Suite und zweifeln, ob er funktioniert?

Wir prüfen die Vertragsfläche gegen die echte Spezifikation des Anbieters und die Installation in den Odoo-Serien, die Sie unterstützen. Schreiben Sie an comercial@flexigobe.com oder rufen Sie +34 616 809 504 an.

Mit einem Ingenieur sprechen