Vai al contenuto principale
Odoo · Postmortem di ingegneria

I test erano verdi e il connettore non funzionava: 32 errori che la suite non vedeva

Una suite verde dimostra che il tuo codice si comporta come il tuo mock. Non dimostra che l'API remota sia ancora quella che credevi. Ecco il caso, con i numeri, e le prove che lo avrebbero individuato.

Schermata delle Apps di Odoo con i moduli installati e attivi in un backend reale

Se i tuoi test di Odoo passano e il connettore fallisce in produzione, il motivo è quasi sempre lo stesso: la suite non testa l'API, testa il tuo mock dell'API. Il mock l'hai scritto tu, sulla documentazione di quel giorno, e da allora non è cambiato. L'API sì. Un .get("shippingDate") il cui campo ora si chiama in un altro modo non solleva nessuna eccezione: restituisce None per sempre, e il test che lo copre resta verde perché anche il mock dice shippingDate.

Idea chiave: un test contro un mock dimostra coerenza interna, non conformità esterna. Solo una delle due ti avvisa quando l'altro lato cambia.

Il caso: 236 su 236 verdi in Odoo 19, 18 e 17

Abbiamo fatto l'audit di un nostro connettore per marketplace prima di metterlo davanti a un venditore reale. La suite dava 236 su 236 verdi, e non in locale: su build reali di Odoo.sh nelle tre serie che supportiamo.

Non funzionava. L'audit ha trovato 32 errori reali sotto il verde: 20 di contratto e 12 di versione. Nessuno dei 236 test li toccava, perché nessuno guardava fuori dal modulo.

Cosa si era rotto davvero: 20 errori di contratto e 12 di versione

Un errore di contratto è una discrepanza tra ciò che il modulo invia o legge e ciò che l'API dichiara. Ne sono emersi tre tipi:

  • Un parametro di query obbligatorio che non veniva inviato. L'API risponde 400 e l'errore è rumoroso… in produzione.
  • Un parametro che la specifica non dichiara in nessuna operazione. Questo non dà errore: viene ignorato. La risposta arriva non filtrata e sembra che abbia funzionato. Questo è il pericoloso.
  • Un campo di risposta letto con il nome sbagliato. .get() restituisce None e non salta nulla, mai.

Quattro di quei venti non sono emersi leggendo la specifica: sono emersi chiamando. L'intestazione Link di paginazione arrivava relativa e, così com'era, non era un URL valido: dalla pagina uno in poi non si importava nemmeno un ordine. E un 403 per superamento della quota non arrivava nel formato di errore documentato, quindi veniva letto come un errore di permessi: il connettore segnava l'account come non autorizzato e fermava tutta la sincronizzazione.

Un errore di versione è un'altra cosa: lo stesso codice si comporta in modo diverso a seconda della serie di Odoo. Questi sono emersi dal modulo stesso:

  • models.Constraint(...) esiste in Odoo 19. In 18 e 17 non è un vincolo: la classe si carica, non c'è nessun errore, e la garanzia di unicità è sparita.
  • res.groups scrive i suoi membri users prima di Odoo 19 e user_ids da 19 in poi. Se sbagli lato, il modulo non si installa.
  • Un ir.cron senza numbercall in Odoo 17 prende il valore predefinito 1: il cron gira una volta e si disattiva da solo. Nessuno vede un errore. Semplicemente smette di sincronizzare il martedì.
  • Odoo 17 scrive <tree> dove 18 e 19 scrivono <list>, e rifiuta una vista il cui invisible legge un campo che la vista non mostra: 18 e 19 lo tollerano.

Nota lo schema: metà di quell'elenco non dà errore. Dà silenzio. E il silenzio non lo intercetta nessuna asserzione che non lo cerchi.

Perché la suite non lo ha visto: l'anatomia di un test che si testa da solo

Un tipico test di connettore ha tre parti: un mock che restituisce un JSON, la chiamata al metodo e un'asserzione sul risultato. Se quel JSON l'hai scritto guardando il tuo parser invece della specifica del fornitore, il test è uno specchio: passa sempre, e il giorno in cui l'API cambia passa lo stesso.

Esiste una seconda forma di verde vuoto, ed è specifica di Odoo: la suite non è stata eseguita affatto. Nel runner di Odoo.sh i test post_install girano solo su un'installazione reale: un -u, o un -i su un modulo già installato, esegue zero test ed esce con codice 0. E i selettori --test-tags "/modulo:Clase.metodo" non corrispondono a nulla lì; funziona solo /modulo. Una pipeline scritta male riporta «nessun errore» perché non c'erano test.

Lo abbiamo imparato con un dato scomodo e nostro: uno dei nostri verificatori di conformità riportava zero rilievi per un suo bug. Risolveva solo components/schemas, mentre le risposte 200 di quella API puntano a components/responses, quindi saltava quasi tutti gli endpoint. «Zero rilievi» e «zero endpoint verificati» si scrivono allo stesso modo.

Il secondo caso, peggiore: un doppio rimborso con la suite verde

Secondo audit, altro connettore, di una piattaforma di marketplace. 163 su 163 verdi, anche nelle tre serie e su build reali. Sotto la superficie: il registro delle chiamate non aveva mai scritto una riga, la sincronizzazione incrementale chiedeva la finestra di date sbagliata, e un rimborso poteva essere inviato due volte.

Quello sul denaro è il caso che un mock non può riprodurre. Il client HTTP riprovava davanti a 5xx e a timeout, e per qualsiasi metodo: anche per il PUT che invia un rimborso. Un timeout non dice che la richiesta non sia arrivata, dice che non sai se è arrivata; riprovare lì una scrittura non idempotente significa rimborsare due volte. E i reindirizzamenti venivano seguiti riscrivendo il metodo: un PUT di rimborso poteva finire in un GET senza credenziali che restituiva 200 e veniva registrato come successo. Nessun test lo ha visto, e non è stata una svista: un mock risponde al primo colpo, sempre, senza timeout, senza 502 e senza reindirizzamenti. Il ramo che fa danni è quello che il mock non esegue mai.

Quello delle date sta in una parola: le sincronizzazioni incrementali chiedevano ciò che era stato creato dall'ultimo passaggio invece di ciò che era cambiato. Un ordine creato al mattino e spedito al pomeriggio non rientrava più nella finestra, e il suo stato in Odoo restava congelato.

Quello del log è il più silenzioso dei tre. L'helper che restituisce il modello di registro restituisce un recordset, e quando tutto va bene quel recordset è vuoto; in Odoo, un recordset vuoto è falso. Il controllo era if not log_model: return False, quindi aveva sempre ragione e tutti i percorsi «questo lo registriamo» erano un no-op: né eccezione, né traccia, né riga. La prova che andava avanti così dal primo giorno non è uscita da nessun test, ma dall'aver guardato in produzione il contatore della sequenza di quella tabella e averlo trovato a NULL dopo mesi di cron.

I tre tipi di prova che lo avrebbero individuato

1. Contratto contro la specifica reale, non contro il tuo mock

Non servono né broker né infrastruttura: basta avere l'OpenAPI del fornitore nel repository e confrontarlo, in modo statico, con il tuo client. Tre controlli che girano in pochi secondi ed escono con codice 1 se trovano qualcosa:

  • Parametri di query: cosa dichiara ogni operazione rispetto a ciò che invia ogni wrapper. Individua l'obbligatorio mancante e il nome che nessuno dichiara.
  • Corpi di scrittura: le chiavi di primo livello del payload rispetto allo schema della richiesta.
  • Parser di risposta: ogni nome di campo che il codice legge rispetto allo schema della risposta.

L'esecuzione su quel connettore: 87 wrapper mappati su 88 operazioni della specifica, 20 percorsi di scrittura con corpo JSON, 38 parser su 38 operazioni distinte (5 funzioni multi-endpoint non analizzabili), 0 rilievi. Quello zero significa qualcosa perché ha il denominatore stampato accanto.

E quando il fornitore offre una sandbox, una chiamata reale per famiglia di endpoint: è l'unica prova che distingue «la specifica dice questo» da «il server fa questo».

2. Installazione reale, non import (e poi, esercitare)

Che un modulo si importi non dice nulla; che compili, neppure. La prova è installarlo: -i su un database pulito e -i su un database che aveva già la versione precedente. Sono due prove diverse, e la seconda è quella che salta con i dati noupdate e le migrazioni. Lo abbiamo approfondito in un modulo che compila in Odoo 19 e comunque non si installa.

E dopo l'installazione, esercitarlo. Abbiamo passato 47 app del nostro catalogo su installazioni pulite di Odoo 17 e 18 Community (installare e poi chiamare get_views, search_read e onchange): 61 esecuzioni di versione su 94 sono risultate pulite. Delle 33 restanti, la maggior parte non arrivava nemmeno a installarsi, quattro erano artefatti dovuti al testare ogni app senza il suo modulo gemello, e due si installavano perfettamente ed esplodevano quando venivano esercitate: un Expected singleton in un compute di product.template e un modello che presupponeva Enterprise su un database Community.

3. Verifica nelle tre versioni, una per una

Un modulo che va su 19, 18 e 17 sono tre moduli che si somigliano. L'elenco di trappole qui sopra lo abbiamo come controllo statico che gira in un secondo sul port generato, prima di spendere una build: ogni voce era un errore reale e metà non falliva in modo rumoroso. È anche il fondo dell'argomento quando qualcuno confronta connettore nativo e livello intermedio: la superficie di contratto non sparisce, cambia solo di proprietario. Ne parliamo in Odoo 19 contro i connettori middleware.

Cosa cambiare lunedì

  • Salva la specifica del fornitore nel repository, versionata insieme al codice che la usa. Il suo diff è il tuo allarme.
  • Dai a ogni fixture una data e una versione di contratto. Un mock che non dice contro cosa è stato scritto è già scaduto e non lo sai.
  • Scrivi un comparatore statico tra il tuo client e quella specifica. Duecento righe di Python che escano con codice diverso da zero.
  • Fai fallire la pipeline se il numero di test eseguiti è zero, o se scende rispetto all'esecuzione precedente. «0 errori» non significa nulla; «0 errori su 236», sì.
  • Installa davvero nella CI (database pulito e database con la versione precedente) ed esercita dopo: apri le viste e leggi i record via ORM. E se mantieni più serie di Odoo, esegui la suite in tutte.

E il cambiamento che costa davvero, che non è tecnico: «la suite è verde» non risponde a «funziona?», risponde a «il mio codice fa ancora ciò che credevo?». Sono due domande, e confonderle ci è costato 32 errori in un connettore e un doppio rimborso latente in un altro.

Se mantieni connettori per clienti e vuoi una seconda lettura su ciò che la tua suite non sta testando, così lavoriamo con società di consulenza e team tecnici e con altri partner di Odoo.

Link utili all'interno di FlexigoTech

Lavoro con i partner di OdooCome ci integriamo con team che hanno già il proprio ciclo di sviluppoIngegneria di Odoo per società di consulenzaCapacità tecnica per società di consulenza che hanno bisogno di connettori mantenutiOdoo 19 contro i connettori middlewareDove vive la superficie di contratto quando inserisci un livello intermedioConnettore Mirakl per OdooCome affrontiamo la sincronizzazione di ordini, resi e liquidazioni

Domande frequenti

Perché i miei test di Odoo passano e il connettore fallisce in produzione?

Perché la suite testa il tuo mock, non l'API. Il mock l'hai scritto sulla documentazione di quel giorno e da allora non è cambiato; l'API sì. Un campo letto con il nome sbagliato restituisce None senza sollevare eccezioni, quindi nessuna asserzione se ne accorge. L'unico modo per individuarlo è confrontare il tuo client con la specifica ufficiale del fornitore, o fare almeno una chiamata reale alla sandbox.

Cos'è il contract testing applicato a un connettore di Odoo?

Nella sua versione pratica non richiede broker né infrastruttura: consiste nel salvare l'OpenAPI del fornitore nel repository e confrontare in modo statico con esso i parametri che invii, le chiavi dei corpi di scrittura e i nomi di campo che legge ogni parser. Tre script che escono con codice diverso da zero. Coprono la maggior parte degli errori di contratto prima di avviare Odoo.

Come faccio a sapere se il mio mock è disallineato rispetto all'API?

Tre segnali: il fixture non indica con quale versione della specifica è stato scritto; la specifica del fornitore non è versionata nel repository, quindi non puoi vedere il suo diff; e nessun test tocca mai la rete. Se si verificano tutti e tre, il tuo mock è già disallineato anche se la suite è verde.

Basta testare il modulo in Odoo 19 se lo pubblico anche in 18 e 17?

No. Ci sono costrutti che esistono in 19 e in 18 o 17 non falliscono: vengono ignorati. models.Constraint in 18 o 17 non è un vincolo e non dà errore, quindi perdi l'unicità in silenzio. Un ir.cron senza numbercall in 17 si spegne da solo dopo la prima esecuzione. Passando 47 app su installazioni pulite di 17 e 18 Community, 61 esecuzioni su 94 sono risultate pulite: un terzo no.

Un test di installazione è la stessa cosa di un test di import?

No, e confonderli è uno degli errori più costosi. Importare il modulo prova solo che il Python è valido. Installarlo carica l'XML, valida le viste, applica la sicurezza ed esegue le migrazioni. E anche installato, bisogna esercitarlo: nel nostro catalogo c'erano app che si installavano perfettamente ed esplodevano aprendo una vista o leggendo un record via ORM.

Cosa facciamo in merito

Integrazioni su misuraCosa fa, schermate, versioni e prezzo.

Hai un connettore con la suite verde e dubiti che funzioni?

Verifichiamo la superficie di contratto rispetto alla specifica reale del fornitore e l'installazione nelle serie di Odoo che supporti. Scrivi a comercial@flexigobe.com o chiama il +34 616 809 504.

Parla con un ingegnere