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()restituisceNonee 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.groupsscrive i suoi membriusersprima di Odoo 19 euser_idsda 19 in poi. Se sbagli lato, il modulo non si installa.- Un
ir.cronsenzanumbercallin 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 cuiinvisiblelegge 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
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
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.

