- Introduzione
- unittest.,finto o mock
- Decoratore
- posizione della Risorsa
- Finto return_value vs side_effect
- Finto Chiamate Nidificate
- Verificare Eccezioni
- Eliminare lru_cache
- Falsa a Livello di Modulo e/Variabili Globali
- Finto Metodo di Istanza
- Finto Metodo di Classe
- Mock Intera Classe
- Finto Chiamate Asincrone
- Finto Tipi di Istanza
- Finto builtin
open
funzione - Conclusione
Introduzione
Beffardo risorse quando la scrittura di test in Python può essere fonte di confusione se non avete dimestichezza con il fare queste cose., In questo post ho intenzione di coprire vari aspetti del codice beffardo, che si spera sia una risorsa utile per coloro che sono un po ‘ bloccati.
Nota: negli esempi di codice sto usando pytest, ma per la maggior parte non dovrebbe avere importanza.
unittest.mock o mock
Per ‘prendere in giro’ una risorsa avremo bisogno prima del modulo mock
, e questo è il nostro primo ostacolo: di quale versione abbiamo bisogno? cioè ce ne sono due e sembrano entrambi ufficiali (mock
e unittest.mock
).,
Il modulomock
è una libreria retrocompatibile che puoi scaricare da PyPy, dove asunittest.mock
è la stessa cosa ma compatibile solo con la versione di Python che stai usando.,
in quasi tutti i casi si desidera importare in questo modo:
import unittest.mock as mock
Per ulteriori esempi, vedi questa guida di riferimento
Decoratore
Il modo più comune per deridere le risorse è quello di utilizzare un Pitone decoratore intorno alla vostra funzione di test:
@mock.patch("thing")def test_stuff(mock_thing): mock_thing.return_value = 123
In questo caso, di cosa stiamo patch (thing
) può essere una variabile o una funzione.,
Quando lo fai dovrai passare un argomento alla tua funzione (puoi chiamarlo come vuoi †) che sarà un MagicMock
.
Questo significa che se non fai nient’altro, le chiamate a thing
(almeno nell’esempio precedente) comporteranno il ritorno del valore 123
.
† la convenzione consiste nel nominare la variabile
mock_<noun>
.,
Se sei un beffardo di più le cose, quindi ti stack il finto decoratori ontop di ogni altro, e passare lungo per la funzione di test:
@mock.patch("third")@mock.patch("second")@mock.patch("first")def test_stuff(mock_first, mock_second, mock_third): ...
posizione della Risorsa
e ‘ importante sapere che, quando beffardo, è necessario specificare la posizione della risorsa di essere preso in giro, rilevanti per cui e ‘ importata., Questo è meglio spiegato con l’esempio…
Immaginare ho un modulo app.foo
e all’interno del modulo di importazione di un’altra dipendenza, in questo modo:
from app.bar import thing
Si potrebbe pensare che quando si chiama mock.patch
che si passa un riferimento alla risorsa come app.bar.thing
. Ciò sarebbe rilevante solo se la risorsa veniva chiamata con quel percorso completo all’interno del modulo app.foo
(ad esempio se app.foo
chiamato app.bar.thing(...)
).,
Se il percorso completo dello spazio dei nomi non è referenziato, che non è nell’esempio precedente (si noti che importiamo solo la risorsathing
). Significa che abbiamo bisogno di specificare lo spazio dei nomi di riferimento per deridere come, dove e ‘ importata:
@mock.patch('app.foo.thing')
anche se thing
esiste all’interno del app.bar
specifichiamo app.foo.thing
come app.foo
è dove abbiamo importato per l’uso. Questo cattura la gente fuori tutto il tempo.,
Finto return_value vs side_effect
Se la funzione ha un try/except intorno ad esso, quindi è possibile utilizzare side_effect
a causa della chiamata di funzione per attivare un’Eccezione come il valore restituito:
@mock.patch('app.aws.sdk.confirm_sign_up', side_effect=Exception('whoops'))
Nota: se si fosse usato
return_value=Exception('whoops')
poi il finto restituisce la rappresentazione in forma di stringa di Eccezione, piuttosto che sollevare eccezioni comeside_effect
fa.,metodo deriso oggetto è stato chiamato:La ragione per cui questo può essere più complicato è dovuto a come un finto restituisce un nuovo mock quando l’accesso a una proprietà di un mock:
Il precedente codice di errore:
AssertionError: expected call not found.Expected: listen(8080)Actual: listen(123)
È necessario assicurarsi che si affermano il finto al momento giusto:
Verificare Eccezioni
Se si desidera verificare che qualche pezzo di codice genera un
Exception
tipo quando ne abbiamo bisogno possiamo mock risorse specifiche per lanciare un’eccezione e quindi utilizzarepytest.raises
come contesto il gestore di tutto il chiamante del nostro codice di verifica.,Possiamo catturare e fare asserzioni contro questo comportamento previsto prendendo in giro prima la risorsa che vogliamo lanciare un’eccezione e farla lanciare la nostra falsa eccezione usando il parametro
side_effect
.Successivamente specifichiamo l’esatto tipo di eccezione che ci aspettiamo di generare utilizzando
pytest.raises(T)
:Nota: non commettere l’errore di inserire alcuna asserzione all’interno del
with
context manager., Una volta che l’eccezione viene sollevata dalla funzione chiamata all’interno delwith
context manager, tutto il codice dopo di esso all’interno del blocco viene saltato.Eliminare lru_cache
Se la funzione che si desidera testare la
functools.lru_cache
decoratore applicata, quindi, è necessario essere consapevoli di giro la risposta di tale funzione sarà memorizzato nella cache in un test e il risultato memorizzato nella cache verrà restituito quando viene chiamata la funzione di nuovo a testare qualche altro comportamento (e potrebbe confondere, quando vedi la risposta imprevista).,Per risolvere questo problema è molto semplice perché
lru_cache
fornisce funzioni aggiuntive quando decoratoring le funzioni, fornisce:
cache_info
cache_clear
L’ultimo (
cache_clear
) è quello che si potrebbe chiamare. Questo è dimostrato di seguito:Nota: debug questo non è sempre ovvio., Più tardi dimostrerò come prendere in giro la funzione
open
incorporata, e in quello scenario mi sono imbattuto in questo problema, perché sebbene non stessi prendendo in giro la funzione di livello superiore stessa (stavo prendendo in giro la chiamata aopen
all’interno), il contenuto del file aperto era ciò che è stato restituitoMock Module Level/Global Variables
Con una variabile modulo è possibile impostare il valore direttamente o utilizzare
mock.patch
.,Nell’esempio seguente abbiamo la variabile
client_id
che è una variabile globale all’interno delapp.aws
modulo che ci importa di riferimento altrove nel nostro codice:Nel
mock.patch
esempio, ci sono due cose da notare:
- non usare
return_value
.- non esiste un’istanza finta passata alla funzione di test.,
Questo perché stiamo modificando una variabile e non una funzione diretta o “callable”, quindi non è necessario passare una simulazione nella funzione di test (se si desidera modificare il valore alcune volte all’interno del test stesso, si deriderebbe la variabile ma non assegnare immediatamente un valore nel decoratore).
Mock Instance Method
Esistono diversi modi per ottenere il mocking di un metodo di istanza., Un approccio comune è quello di utilizzare
mock.patch.object
, in questo modo:un Altro approccio è quello di deridere il metodo come una normale funzione, ma si fa riferimento al metodo tramite il classname:
un Altro (anche se più pesante) approccio per beffardo di un metodo d’istanza di classe è quello di approfittare del fatto che un Finto restituisce un nuovo mock esempio quando chiamato:
@mock.patch("foo.bar.SomeClass")def test_stuff(mock_class): mock_class.return_value.made_up_function.return_value = "123"
Nota: nell’esempio sopra si è finto l’intera classe, che potrebbe non essere ciò che si desidera. In caso contrario, utilizzare l’esempio precedente
mock.patch.object
.,Il motivo per cui l’esempio sopra funziona è perché stiamo impostando
return_value
sul nostro mock. Perché questo è unMagicMock
ogni attributo di riferimento restituisce un nuovo mock istanza di una funzione o di una proprietà chiamata su un mock non deve esistere), e così noi chiamiamomade_up_function
restituito finto, e che appena creato mock abbiamo impostato il finalereturn_value
123
.,Ma come menzionato nella nota sopra, questo approccio potrebbe essere un po ‘ troppo schietto a seconda di quali sono le tue esigenze (se ti interessa se hai una classe di funzionamento o meno).
Mock Class Method
Prendere in giro un metodo di classe è un approccio simile a prendere in giro un metodo di istanza.,
Un approccio potrebbe essere quello di deridere l’intera classe (ma ora si dispone di uno di meno
return_value
per assegnare):mock_class.ClassMethodName.return_value = "123"
O, meglio ancora, si dovrebbe finto come si farebbe con qualsiasi normale funzione, ma solo di riferimento, il metodo tramite la classe:
Finto Intera Classe
Per farsi beffe di un’intera classe è necessario impostare il
return_value
per essere una nuova istanza della classe.,Vedi qui altri suggerimenti di beffa relativi alla classe
Chiamate asincrone simulate
Il codice asincrono beffardo è probabilmente l’aspetto più confuso del beffardo. La mia soluzione “vai a” ti spiegherò prima, ma dopo condividerò alcuni metodi alternativi che ho visto e provato in passato.,
Prima di considerare questo codice asincrono all’interno di un
app.foo
modulo:import app.stuffasync def do_thing(x): return await app.stuff.some_concurrent_function(x)
Se abbiamo bisogno di prendere in giro la coroutine
app.stuff.some_concurrent_function
, quindi siamo in grado di risolvere il problema creando una funzione che agisce come una coroutine e permettono di essere configurabile per diversi tipi di risposte:Nota: l’esempio utilizza tornado per l’esecuzione di un test asincrono.,e le alternative…
AsyncMock
Nota: questo utilizza il pacchetto
pytest-asyncio
per aiutare con i test asyncio codiceiniziamo con il codice di essere preso in giro…
import asyncioasync def sum(x, y): await asyncio.sleep(1) return x + y
Ora ecco come avevamo finto di…
Scimmia Patch
MagicMock Sottoclasse
Async Funzione Inline
Finto Istanza Tipi
Quando beffardo di un oggetto vi accorgerete che il finto sostituisce l’intero oggetto e quindi può causare superare delle prove (o negativo) in modi inaspettati.,
Significato, se avete bisogno di fare un mock più come il calcestruzzo interfaccia, quindi ci sono due modi per farlo:
spec
wrap
E ‘ possibile utilizzare mock
spec
funzione di imitare tutti i metodi/attributi dell’oggetto preso in giro. Ciò garantisce che i tuoi mock abbiano la stessa api degli oggetti che stanno sostituendo.Nota: esiste un
spec_set
più rigoroso che genererà unAttributeError
.,Questo è meglio dimostrato con un esempio:
Il
wrap
parametro d’altra parte permette di ‘spiare’ l’implementazione, così come influenzare il suo comportamento.,su che prende in giro l’intero oggetto, non solo un singolo metodo:Finto incorporato aperto funzione
Python mock biblioteca fornisce un’astrazione per il beffardo builtin
open
funzione molto più semplice…
create=True
param impostato sumock.patch
significa che ilmock.MagicMock
restituita verrà automaticamente creare attributi che vengono chiamate in finto (questo perché ilopen
funzione tenta di accedere a un sacco di cose diverse ed è più facile per finto per finto, tutti per voi).,Conclusione
Lì finiremo. Speriamo che questo elenco di tecniche di beffa sarà in grado di vedere attraverso anche il più complesso di codice che devono essere testati. Fammi sapere cosa ne pensi su Twitter.