Przejdź do głównej treści
Odoo · Postmortem inżynieryjny

Testy były zielone, a konektor nie działał: 32 błędy, których suita nie widziała

Zielona suita dowodzi, że Twój kod zachowuje się tak jak Twój mock. Nie dowodzi, że zdalne API nadal jest takie, jak sądzisz. Oto przypadek, z liczbami, oraz testy, które by to wykryły.

Ekran Apps w Odoo z zainstalowanymi i aktywnymi modułami na prawdziwym backendzie

Jeśli Twoje testy Odoo przechodzą, a konektor zawodzi na produkcji, powód jest prawie zawsze ten sam: suita nie testuje API, testuje Twój mock API. Mock napisałeś Ty, na podstawie dokumentacji z tamtego dnia, i od tamtej pory się nie zmienił. API tak. Wywołanie .get("shippingDate") na polu, które teraz nazywa się inaczej, nie rzuca żadnego wyjątku: zawsze zwraca None, a test, który to obejmuje, pozostaje zielony, bo mock też mówi shippingDate.

Kluczowa myśl: test względem mocka dowodzi wewnętrznej spójności, nie zgodności z zewnętrznym światem. Tylko jedno z tych dwojga ostrzeże Cię, gdy druga strona się zmieni.

Przypadek: 236 z 236 na zielono w Odoo 19, 18 i 17

Zaudytowaliśmy nasz własny konektor do marketplace'ów, zanim trafił do prawdziwego sprzedawcy. Suita pokazywała 236 z 236 na zielono, i to nie lokalnie: na prawdziwych buildach Odoo.sh w trzech wspieranych przez nas seriach.

Nie działał. Audyt znalazł 32 prawdziwe błędy pod zielonym wynikiem: 20 kontraktowych i 12 wersyjnych. Żaden z 236 testów ich nie dotykał, bo żaden nie patrzył poza moduł.

Co naprawdę się zepsuło: 20 błędów kontraktu i 12 wersji

Błąd kontraktu to rozbieżność między tym, co moduł wysyła lub czyta, a tym, co deklaruje API. Wyszły trzy odmiany:

  • Wymagany parametr zapytania, który nie był wysyłany. API odpowiada 400 i błąd jest głośny… na produkcji.
  • Parametr, którego specyfikacja nie deklaruje w żadnej operacji. To nie daje błędu: jest ignorowany. Odpowiedź przychodzi niefiltrowana i wygląda, jakby zadziałało. Ten jest niebezpieczny.
  • Pole odpowiedzi odczytane pod błędną nazwą. .get() zwraca None i nic się nie wywala, nigdy.

Cztery z tych dwudziestu nie wyszły z czytania specyfikacji: wyszły z wywoływania. Nagłówek Link paginacji przychodził jako względny i w tej postaci nie był poprawnym adresem URL: od strony pierwszej nie importowało się ani jedno zamówienie. A 403 za przekroczenie limitu nie miał udokumentowanego formatu błędu, więc był odczytywany jako błąd uprawnień: konektor oznaczał konto jako nieautoryzowane i zatrzymywał całą synchronizację.

Błąd wersji to coś innego: ten sam kod zachowuje się inaczej zależnie od serii Odoo. Te wyszły z samego modułu:

  • models.Constraint(...) istnieje w Odoo 19. W 18 i 17 nie jest ograniczeniem: klasa się ładuje, nie ma żadnego błędu, a gwarancja unikalności znika.
  • res.groups zapisuje swoje składowe jako users przed Odoo 19 i jako user_ids od 19. Jeśli pomylisz stronę, moduł się nie zainstaluje.
  • ir.cron bez numbercall w Odoo 17 przyjmuje wartość domyślną 1: cron uruchamia się raz i sam się wyłącza. Nikt nie widzi błędu. Po prostu przestaje synchronizować we wtorek.
  • Odoo 17 zapisuje <tree> tam, gdzie 18 i 19 zapisują <list>, i odrzuca widok, którego invisible czyta pole niewyświetlane w widoku: 18 i 19 to tolerują.

Zwróć uwagę na wzorzec: połowa tej listy nie daje błędu. Daje ciszę. A ciszy nie wyłapie żadna asercja, która jej nie szuka.

Dlaczego suita tego nie zobaczyła: anatomia testu, który testuje samego siebie

Typowy test konektora ma trzy elementy: mock zwracający JSON, wywołanie metody i asercję na wyniku. Jeśli ten JSON napisałeś, patrząc na własny parser zamiast na specyfikację dostawcy, test jest lustrem: przechodzi zawsze, a w dniu, gdy API się zmieni, też.

Jest druga forma pustej zieleni, specyficzna dla Odoo: suita w ogóle się nie wykonała. W runnerze Odoo.sh testy post_install uruchamiają się tylko na prawdziwej instalacji: -u lub -i na już zainstalowanym module wykonuje zero testów i kończy się kodem 0. A selektory --test-tags "/modulo:Clase.metodo" tam do niczego nie pasują; działa tylko /modulo. Źle napisany pipeline raportuje „bez błędów”, bo nie było testów.

Dowiedzieliśmy się o tym dzięki niewygodnemu i naszemu własnemu faktowi: jeden z naszych własnych weryfikatorów zgodności raportował zero ustaleń z powodu własnego błędu. Rozwiązywał tylko components/schemas, podczas gdy odpowiedzi 200 tego API wskazują na components/responses, więc pomijał prawie wszystkie endpointy. „Zero ustaleń” i „zero sprawdzonych endpointów” zapisuje się tak samo.

Drugi przypadek, gorszy: podwójny zwrot środków przy zielonej suicie

Drugi audyt, inny konektor, platformy marketplace'ów. 163 z 163 na zielono, także w trzech seriach i na prawdziwych buildach. Pod spodem: log wywołań nigdy nie zapisał ani jednego wiersza, synchronizacja przyrostowa pytała o złe okno dat, a zwrot środków mógł zostać wysłany dwa razy.

Ten związany z pieniędzmi to ten, którego mock nie potrafi odtworzyć. Klient HTTP ponawiał przy 5xx i przy timeout, dla dowolnej metody: także dla PUT, które wysyła zwrot. Timeout nie mówi, że żądanie nie dotarło, mówi, że nie wiesz, czy dotarło; ponawianie tam zapisu nieidempotentnego to zwrot środków dwa razy. A przekierowania były śledzone z przepisywaniem metody: PUT ze zwrotem mógł skończyć jako GET bez poświadczeń, które zwracało 200 i było notowane jako sukces. Żaden test tego nie zobaczył i nie było to zaniedbanie: mock odpowiada za pierwszym razem, zawsze, bez timeoutów, bez 502 i bez przekierowań. Gałąź, która szkodzi, to ta, której mock nigdy nie wykonuje.

Ten z datami mieści się w jednym słowie: synchronizacje przyrostowe pytały o to, co utworzone od ostatniego przebiegu, zamiast o to, co zmienione. Zamówienie utworzone rano i wysłane po południu nie wracało już do okna, a jego status w Odoo zamarzał.

Ten z logiem jest najcichszy z trzech. Pomocnik zwracający model rejestru zwraca recordset, a gdy wszystko idzie dobrze, ten recordset jest pusty; w Odoo pusty recordset jest fałszem. Sprawdzenie miało postać if not log_model: return False, więc zawsze trafiało i wszystkie ścieżki „to rejestrujemy” były no-opem: ani wyjątku, ani śladu, ani wiersza. Dowód, że tak było od pierwszego dnia, nie pochodził z żadnego testu, lecz z zajrzenia na produkcji do licznika sekwencji tej tabeli i znalezienia go na NULL po miesiącach działania cronów.

Trzy rodzaje testów, które by to wyłapały

1. Kontrakt względem prawdziwej specyfikacji, nie względem Twojego mocka

Nie potrzeba żadnego brokera ani infrastruktury: wystarczy mieć OpenAPI dostawcy w repozytorium i porównać je statycznie z Twoim klientem. Trzy bramki, które działają w sekundy i kończą się kodem 1, jeśli coś znajdą:

  • Parametry zapytania: co deklaruje każda operacja wobec tego, co wysyła każdy wrapper. Wyłapuje brakujący wymagany parametr i nazwę, której nikt nie deklaruje.
  • Ciała zapytań zapisu: klucze pierwszego poziomu payloadu względem schematu żądania.
  • Parsery odpowiedzi: każda nazwa pola odczytywana przez kod względem schematu odpowiedzi.

Przebieg na tym konektorze: 87 wrapperów zmapowanych na 88 operacji specyfikacji, 20 ścieżek zapisu z ciałem JSON, 38 parserów na 38 różnych operacjach (5 funkcji wieloendpointowych niemożliwych do analizy), 0 ustaleń. To zero coś znaczy, bo ma obok wydrukowany mianownik.

A gdy dostawca udostępnia sandbox, jedno prawdziwe wywołanie na rodzinę endpointów: to jedyny test, który odróżnia „specyfikacja mówi to” od „serwer robi to”.

2. Prawdziwa instalacja, nie import (a potem ćwiczenie modułu)

To, że moduł się importuje, nic nie mówi; że się kompiluje, też nie. Testem jest jego zainstalowanie: -i na czystej bazie i -i na bazie, która miała już poprzednią wersję. To dwa różne testy, a drugi wywala się na danych noupdate i migracjach. Opisaliśmy to w module, który kompiluje się w Odoo 19 i mimo to się nie instaluje.

A po instalacji trzeba go ćwiczyć. Przepuściliśmy 47 aplikacji z naszego katalogu przez czyste instalacje Odoo 17 i 18 Community (zainstaluj, a potem wywołaj get_views, search_read i onchange): 61 z 94 przebiegów wersyjnych wyszło czystych. Z pozostałych 33 większość w ogóle się nie instalowała, cztery były artefaktami testowania każdej aplikacji bez modułu siostrzanego, a dwie instalowały się bez zarzutu i wywalały przy ćwiczeniu: Expected singleton w compute w product.template oraz model zakładający Enterprise na bazie Community.

3. Weryfikacja w trzech wersjach, jedna po drugiej

Moduł, który działa w 19, 18 i 17, to trzy podobne do siebie moduły. Listę pułapek z góry mamy jako statyczną kontrolę, która w sekundę przebiega po wygenerowanym porcie, zanim zmarnujemy build: każda pozycja była prawdziwym błędem, a połowa nie zawodziła głośno. To także sedno argumentu, gdy ktoś porównuje konektor natywny z warstwą pośrednią: powierzchnia kontraktu nie znika, tylko zmienia właściciela. Omawiamy to w Odoo 19 kontra konektory middleware.

Co zmienić w poniedziałek

  • Trzymaj specyfikację dostawcy w repozytorium, wersjonowaną razem z kodem, który z niej korzysta. Jej diff to Twój alarm.
  • Opatrz każdy fixture datą i wersją kontraktu. Mock, który nie mówi, względem czego został napisany, już wygasł, a Ty o tym nie wiesz.
  • Napisz statyczny komparator między Twoim klientem a tą specyfikacją. Dwieście linii Pythona, które kończą się kodem różnym od zera.
  • Spraw, by pipeline padał, jeśli liczba wykonanych testów wynosi zero lub spadła względem poprzedniego przebiegu. „0 błędów” nic nie znaczy; „0 błędów z 236” już tak.
  • Instaluj naprawdę w CI (czysta baza i baza z poprzednią wersją), a potem ćwicz: otwieraj widoki i czytaj rekordy przez ORM. A jeśli utrzymujesz kilka serii Odoo, uruchamiaj suitę we wszystkich.

I zmiana, która naprawdę kosztuje, a która nie jest techniczna: „suita jest zielona” nie odpowiada na pytanie „czy działa?”, odpowiada na „czy mój kod nadal robi to, co myślałem?”. To dwa pytania, a mylenie ich kosztowało nas 32 błędy w jednym konektorze i utajony podwójny zwrot środków w drugim.

Jeśli utrzymujesz konektory dla klientów i chcesz drugiej opinii o tym, czego Twoja suita nie testuje, tak pracujemy z firmami doradczymi i zespołami technicznymi oraz z innymi partnerami Odoo.

Przydatne linki w FlexigoTech

Praca z partnerami OdooJak wpasowujemy się w zespoły, które mają własny cykl rozwojuInżynieria Odoo dla firm doradczychZdolności techniczne dla firm doradczych, które potrzebują utrzymywanych konektorówOdoo 19 kontra konektory middlewareGdzie mieszka powierzchnia kontraktu, gdy wstawiasz warstwę pośredniąKonektor Mirakl dla OdooJak podchodzimy do synchronizacji zamówień, zwrotów i rozliczeń

Najczęstsze pytania

Dlaczego moje testy Odoo przechodzą, a konektor zawodzi na produkcji?

Bo suita testuje Twój mock, nie API. Mock napisałeś na podstawie dokumentacji z tamtego dnia i od tamtej pory się nie zmienił; API tak. Pole odczytane pod błędną nazwą zwraca None bez rzucania wyjątku, więc żadna asercja się o tym nie dowie. Jedyny sposób, by to wykryć, to porównać Twojego klienta ze specyfikacją dostawcy albo wykonać choć jedno prawdziwe wywołanie do sandboxa.

Czym jest contract testing zastosowany do konektora Odoo?

W praktycznej wersji nie potrzebuje brokera ani infrastruktury: polega na trzymaniu OpenAPI dostawcy w repozytorium i statycznym porównywaniu z nim wysyłanych parametrów, kluczy ciał zapisu i nazw pól odczytywanych przez każdy parser. Trzy skrypty, które kończą się kodem różnym od zera. Pokrywają większość błędów kontraktu, zanim uruchomisz Odoo.

Skąd wiem, czy mój mock jest nieaktualny względem API?

Trzy sygnały: fixture nie wskazuje, względem jakiej wersji specyfikacji został napisany; specyfikacja dostawcy nie jest wersjonowana w repozytorium, więc nie widać jej diffu; i żaden test nigdy nie dotyka sieci. Jeśli spełnione są wszystkie trzy, Twój mock jest już nieaktualny, nawet jeśli suita jest zielona.

Czy wystarczy testować moduł w Odoo 19, jeśli publikuję go także w 18 i 17?

Nie. Są konstrukcje, które istnieją w 19, a w 18 lub 17 nie zawodzą: są ignorowane. models.Constraint w 18 lub 17 nie jest ograniczeniem i nie daje błędu, więc po cichu tracisz unikalność. ir.cron bez numbercall w 17 wyłącza się sam po pierwszym wykonaniu. Po przepuszczeniu 47 aplikacji przez czyste instalacje 17 i 18 Community 61 z 94 przebiegów wyszło czystych: jedna trzecia nie.

Czy test instalacji to to samo co test importu?

Nie, a mylenie ich to jeden z najdroższych błędów. Import modułu dowodzi tylko, że Python jest poprawny. Instalacja ładuje XML, waliduje widoki, stosuje zabezpieczenia i wykonuje migracje. A nawet po instalacji trzeba go ćwiczyć: w naszym własnym katalogu były aplikacje, które instalowały się bez zarzutu, a wywalały się przy otwarciu widoku lub odczycie rekordu przez ORM.

Co robimy w tej sprawie

Integracje na zamówienieCo robi, zrzuty ekranu, wersje i cena.

Masz konektor z zieloną suitą i wątpisz, czy działa?

Sprawdzamy powierzchnię kontraktu względem prawdziwej specyfikacji dostawcy oraz instalację w seriach Odoo, które wspierasz. Napisz na comercial@flexigobe.com lub zadzwoń pod numer +34 616 809 504.

Porozmawiaj z inżynierem