È solo un dato di fatto, poiché il codice cresce alla fine dovrai iniziare ad aggiungere mock alla tua suite di test. Quello che era iniziato come un piccolo progetto di due classi sta ora parlando con servizi esterni e non è più possibile testarlo comodamente.

Ecco perché Python viene fornito conunittest.mock, una parte potente della libreria standard per eliminare le dipendenze e deridere gli effetti collaterali.

Tuttavia,unittest.mock non è particolarmente intuitivo.,

Mi sono trovato molte volte a chiedermi perché la mia ricetta go-to non funziona per un caso particolare, quindi ho messo insieme questo cheatsheet per aiutare me stesso e gli altri a prendere in giro rapidamente.

Puoi trovare gli esempi di codice nel repository Github dell’articolo. Userò Python 3.6, se stai usando 3.2 o sotto dovrai usare il pacchetto PyPImock.,

Gli esempi sono scritti usando le classiunittest.TestCase per semplicità nell’eseguirle senza dipendenze, ma potresti scriverle come funzioni usandopytest quasi direttamente,unittest.mock funzionerà bene. Se sei un utentepytest anche se ti incoraggio a dare un’occhiata all’eccellente libreriapytest-mock.

Il punto centrale del modulo unittest.mock è, ovviamente, la classe Mock., La caratteristica principale di un Mock oggetto che possa tornare un altro Mock esempio, quando:

  • accesso a uno dei suoi attributi
  • chiama l’oggetto stesso

Questo è il comportamento di default, ma può essere sottoposto a override in modi diversi. Ad esempio puoi assegnare un valore a un attributo in Mock by:

  • Assegnalo direttamente, come faresti con qualsiasi oggetto Python.,
  • Utilizzare ilconfigure_mock metodo su un’istanza.
  • O passare gli argomenti delle parole chiave alla classeMock alla creazione.

Per ignorare le chiamate al mock hai bisogno di configurare il suo return_value proprietà, disponibile anche come parola chiave argomento nel Mock inizializzatore., Mock restituisce sempre lo stesso valore su tutte le chiamate, questo, di nuovo, può anche essere configurato utilizzando il side_effect attributo:

  • se si desidera restituire valori diversi su ogni chiamata, è possibile assegnare un iterable side_effect.
  • Se si desidera generare un’eccezione quando si chiama il Mock, è sufficiente assegnare l’oggetto exception a side_effect.,

Con tutti questi strumenti ora possiamo creare stub per essenzialmente qualsiasi oggetto Python, che funzionerà alla grande per gli input al nostro sistema. Ma per quanto riguarda le uscite?

Se stai creando un programma CLI per scaricare l’intero Internet, probabilmente non vuoi scaricare l’intero Internet su ogni test. Invece sarebbe sufficiente affermare cherequests.download_internet (non un metodo reale) è stato chiamato in modo appropriato. Mock offre metodi pratici per farlo.,

Nota nel nostro esempio assert_called_once non riuscita, questa vetrine un altro aspetto chiave di Mock oggetti, registrare tutte le interazioni con loro e si può quindi esaminare le interazioni.

Per esempio, è possibile utilizzare call_count per recuperare il numero di chiamate al Mock e usare call_args o call_args_list per ispezionare gli argomenti all’ultimo o di tutte le chiamate rispettivamente.,

Se questo è scomodo in qualsiasi momento è possibile utilizzare il metodoreset_mock per cancellare le interazioni registrate, notare che la configurazione non verrà ripristinata, solo le interazioni.

Infine, permettetemi di presentarvi MagicMock, una sottoclasse di Mock che implementa di default magia o dunder metodi. Ciò rendeMagicMock ideale per deridere il comportamento della classe, motivo per cui è la classe predefinita quando si esegue la patch.,

Ora siamo pronti per iniziare a prendere in giro e isolare l’unità in prova. Ecco alcune ricette da tenere a mente:

Patch all’importazione

Il modo principale per utilizzareunittest.mock è quello di patchare le importazioni nel modulo in prova utilizzando la funzionepatch.

patch intercetteràimport istruzioni identificate da una stringa (ne parleremo più avanti) e restituirà un’istanza Mock è possibile preconfigurare utilizzando le tecniche di cui abbiamo discusso sopra.,

Immaginiamo ora di voler testare questa funzione molto semplice:

Nota stiamo importando os e chiamando getcwd per ottenere la directory di lavoro corrente. Non vogliamo effettivamente chiamarlo sui nostri test, poiché non è importante per il nostro codice e il valore di ritorno potrebbe differire tra gli ambienti su cui viene eseguito.

Come accennato in precedenza, dobbiamo fornire patch con una stringa che rappresenta la nostra importazione specifica., Non vogliamo fornire semplicemente os.getcwd dato che la patch per tutti i moduli, invece, abbiamo voluto fornire il modulo sotto test import di os , cioè work.os . Quando il modulo viene importato patch funzionerà la sua magia e restituirà invece un Mock.,

in Alternativa, possiamo usare il decoratore versione di patch , nota questa volta il test è un parametro aggiuntivo: mocked_os che il Mock è iniettato nella prova.,

patch inoltra parola chiave di argomenti per il Mock classe, in modo da configurare un return_value abbiamo semplicemente aggiungere come uno:

Beffardo classi

e ‘ abbastanza comune per la patch di classi completamente o parzialmente., Helper con "db"

  • Worker restituisce il previsto percorso forniti da Helper
  • per testare Worker in isolamento completo, abbiamo bisogno di patch l’intero Helper classe:

    si noti il doppio return_value nell’esempio, semplicemente utilizzando MockHelper.get_path.return_value potrebbe non funzionare in quanto il codice si chiama get_path su un’istanza, non la classe stessa.,

    La sintassi del concatenamento è leggermente confusa, ma ricordaMagicMockrestituisce un altroMagicMocksulle chiamate__init__. Qui stiamo configurando qualsiasi falsaHelper istanze create daMockHelper per restituire ciò che ci aspettiamo sulle chiamate aget_path che è l’unico metodo che ci interessa nel nostro test.,

    Speccing di classe

    Una conseguenza della flessibilità diMock è che una volta derisa una classe Python non solleveràAttributeError poiché semplicemente restituirà nuove istanze diMagicMock per praticamente tutto. Questo di solito è una buona cosa, ma può portare ad alcuni comportamenti confusi e potenzialmente bug. Ad esempio, scrivendo il seguente test,

    passerà silenziosamente senza alcun avviso mancando completamente l’errore di battitura in assrt_called_once.,

    Inoltre, se si rinomina Helper.get_path Helper.get_folder ma si dimentica di aggiornare la chiamata in Worker i nostri test sarà ancora passare:

    per Fortuna Mock con uno strumento per evitare questi errori, speccing.

    In parole povere, preconfigura i mock per rispondere solo ai metodi che esistono effettivamente nella classe spec., Esistono diversi modi per definire le specifiche, ma il più semplice è semplicemente passare autospec=True alla chiamata patch, che configurerà Mock per comportarsi come l’oggetto che viene deriso, sollevando eccezioni per attributi mancanti e firme errate come richiesto., Per esempio:

    classe Parziale beffardo

    Se si è meno inclini a test in completo isolamento è possibile anche parzialmente patch di una classe utilizzando patch.object:

    patch.object ci permette di configurare deriso versione di get_path solo, lasciando il resto del comportamento incontaminata., Ovviamente questo significa che il test non è più quello che considereresti rigorosamente un test unitario, ma potresti essere d’accordo.

    Beffardo funzioni incorporate e variabili di ambiente

    Negli esempi precedenti abbiamo trascurato di testare un aspetto particolare della nostra classe semplice, la chiamataprint. Se nel contesto della tua applicazione questo è importante, come ad esempio un comando CLI, dobbiamo fare affermazioni contro questa chiamata.,

    print è, ovviamente, una funzione integrata in Python, il che significa che non è necessario importarlo nel nostro modulo, il che va contro ciò che abbiamo discusso sopra sulla patch all’importazione., Ancora immaginare che abbiamo avuto questa leggermente più complicato versione della nostra funzione:

    Siamo in grado di scrivere un test come il seguente:

    Notare un paio di cose:

    1. si può deridere print con nessun problema e affermando che c’era una chiamata, seguendo le “patch importazione” regola. Questo tuttavia è stato un cambiamento introdotto in 3.,5, in precedenza era necessario aggiungere create=Trueallapatchchiamata al segnaleunittest.mockper creare unMock anche se nessuna importazione corrisponde all’identificatore.
    2. stiamo usando patch.dictper iniettare una variabile d’ambiente temporanea inos.environ, questo è estensibile a qualsiasi altro dizionario che vorremmo prendere in giro.,
    3. stiamo nidificando diverse chiamatepatch context manager ma solo usandoas nel primo poiché è quello che dobbiamo usare per chiamareassert_called_once_with .,

    Se non siete appassionati di nidificazione contesto manager che si possono anche scrivere i tag patch chiamate in decoratore forma:

    si noti, tuttavia, l’ordine degli argomenti per il test match che l’ordine di sovrapposizione dei decoratori, e anche che patch.dict non iniettare un argomento.,questo scenario molto comune:

    Si potrebbe, naturalmente, aggiungere un effettivo fixture, ma nel mondo reale casi potrebbe non essere un’opzione, invece siamo in grado di mock contesto il gestore di uscita di un StringIO oggetto:

    non C’è niente di speciale, tranne la magia __enter__ metodo, dobbiamo solo ricordare i meccanismi di base del contesto dirigenti e alcuni uso intelligente del nostro fidato MagicMock .,

    Attributi di classe beffardo

    Ci sono molti modi in cui raggiungere questo obiettivo, ma alcuni sono più infallibili che altri. Supponiamo di aver scritto il codice riportato di seguito:

    Si potrebbe verificare il codice senza deride in due modi:

    1. Se il codice sotto test accede l’attributo via self.ATTRIBUTE è il caso, in questo esempio, si può semplicemente impostare l’attributo direttamente l’istanza. Questo è abbastanza sicuro in quanto la modifica è limitata a questa singola istanza.,
    2. In alternativa è anche possibile impostare l’attributo nella classe importata nel test prima di creare un’istanza. Questo tuttavia cambia l’attributo di classe che hai importato nel tuo test che potrebbe influire sui seguenti test, quindi dovresti ricordarti di ripristinarlo.

    Lo svantaggio principale di questo approccio non finto è che se in qualsiasi momento rinomini l’attributo i tuoi test falliranno e l’errore non indicherà direttamente questa mancata corrispondenza dei nomi.,

    Per risolvere questo problema possiamo utilizzare patch.object sulla classe importata che si lamenterà se la classe non ha l’attributo specificato.

    Ecco alcuni test che utilizzano ciascuna tecnica:

    Mocking class helper

    Il seguente esempio è la radice di molti problemi riguardanti il monkeypatching usando Mock. Di solito si presenta su codebase più mature che iniziano a fare uso di framework e helper alla definizione della classe., Per esempio, immaginate che questo ipotetico Field classe helper:

    il Suo scopo è quello di avvolgere e migliorare un attributo di un’altra classe, un modello abbastanza comune potrebbe vedere in Orm o modulo di librerie. Non preoccuparti troppo dei dettagli di esso, basta notare che c’è un valoretype edefault passato.,

    Ora prendete questo esempio di codice di produzione:

    Questa classe fa uso del Field classe definendo la sua country attributo come uno il cui tipo è str e default come primo elemento COUNTRIES costante. La logica in esame è che a seconda del paese viene applicato uno sconto.,

    Per il quale potremmo scrivere il seguente test:

    Ma NON passerebbe.,

    Lasciare a piedi attraverso la prova:

    1. Prima che la patch di default di paesi pricer per un elenco con una sola voce GB
    2. Questo dovrebbe rendere il CountryPricer.country attributo predefinito per tale voce in quanto è la definizione include default=COUNTRIES
    3. poi instanciates CountryPricer e chiede il prezzo scontato per il GB !

    Allora, cosa sta succedendo?,

    La causa principale di questo si trova in Python comportamento durante l’importazione, meglio descritto in Luciano Ramalho eccellente Fluente Python nel capitolo 21:

    Per le classi, la storia è diversa: al momento dell’importazione, l’interprete esegue il corpo di ogni classe, anche il corpo di classi nidificate in altre classi. L’esecuzione di un corpo di classe significa che gli attributi e i metodi della classe sono definiti e quindi viene creato l’oggetto di classe stesso.,

    l’Applicazione di questo nostro esempio:

    1. country attributo Field istanza è costruito prima che il test è stato eseguito al momento dell’importazione,
    2. Python legge il corpo della classe, passando il COUNTRIES che è definita a quel punto il Field esempio
    3. il Nostro codice di test funziona, ma è troppo tardi per noi patch COUNTRIES e di ottenere una corretta affermazione.,

    Dalla descrizione di cui sopra si potrebbe provare a ritardare l’importazione del modulo fino a che all’interno del test, qualcosa come:

    Questo, tuttavia, ancora non passare come mock.patch importare il modulo e poi monkeypatch, creando la stessa situazione di prima.,

    Per risolvere questo abbiamo bisogno di abbracciare lo stato di CountryPricer classe al momento del test e patch default già inizializzato Field esempio:

    Questa non è la soluzione ideale in quanto richiede la conoscenza del funzionamento interno di Field che non si può o non avere desidera utilizzare se si utilizza una libreria esterna, ma funziona su questo certamente semplificato caso.,

    Questo problema del tempo di importazione è uno dei problemi principali che ho riscontrato durante l’utilizzo di unittest.mock. È necessario ricordare durante l’utilizzo che al momento dell’importazione viene eseguito il codice temporale al livello superiore dei moduli, inclusi i corpi di classe.

    Se la logica che stai testando dipende da questa logica, potresti dover ripensare a come stai usandopatch di conseguenza.,

    Avvolgendo

    Questa introduzione e cheatsheet dovrebbero farti prendere in giro conunittest.mock, ma ti incoraggio a leggere attentamente la documentazione, ci sono molti trucchi più interessanti da imparare.

    Vale la pena ricordare che ci sono alternative a unittest.mock, in particolare la libreria pretend di Alex Gaynor in combinazione con pytestmonkeypatch fixture. Come sottolinea Alex, usando queste due librerie puoi adottare un approccio diverso per rendere il beffardo più rigoroso ma più prevedibile., Sicuramente un approccio che vale la pena esplorare ma al di fuori dello scopo di questo articolo.

    unittest.mock è attualmente lo standard per il mocking in Python e lo troverai praticamente in ogni base di codice. Avere una solida comprensione di esso ti aiuterà a scrivere test migliori più velocemente.

    Articles

    Lascia un commento

    Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *