È 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 il
configure_mock
metodo su un’istanza. - O passare gli argomenti delle parole chiave alla classe
Mock
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 ricordaMagicMock
restituisce un altroMagicMock
sulle 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:
- 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 aggiungerecreate=True
allapatch
chiamata al segnaleunittest.mock
per creare unMock
anche se nessuna importazione corrisponde all’identificatore. - stiamo usando
patch.dict
per iniettare una variabile d’ambiente temporanea inos.environ
, questo è estensibile a qualsiasi altro dizionario che vorremmo prendere in giro., - stiamo nidificando diverse chiamate
patch
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:
- 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., - 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: