Python beffardo c’è qualcosa di istintivo su di te. Non sei facile da fare i conti con e ci vogliono diversi ” Ah, ora ho capito!”momenti prima che tu sia meglio compreso., Sei uno strumento potente e fantastico per abilitare i test unitari automatizzati, ma conosco molti sviluppatori che “cavano” le conoscenze che hanno e la scrittura di questi test richiede più tempo di quanto dovrebbe.
Ci sono molti articoli sul beffardo, e sì, ora ne sto scrivendo un altro. Tuttavia, voglio davvero avere un andare a spiegare chiaramente, soprattutto quelli “Aha!”momenti che hanno spinto in avanti la mia comprensione.
In primo luogo, che cosa è beffardo? Ecco una citazione dai documenti:
mock è una libreria per il test in Python., Ti consente di sostituire parti del tuo sistema in prova con oggetti fittizi e fare affermazioni su come sono stati utilizzati.
Quindi “ti permette di sostituire parti dei tuoi sistemi”, ma quali parti? Si scopre che puoi prendere in giro praticamente tutto ciò che puoi definire, come ad esempio:
- funzioni
- classi
- oggetti
Li sostituisci con “oggetti fittizi” che sono istanze della classe Mock o MagicMock.,
Quindi “fai asserzioni” su quell’istanza finta, che è un modo per verificare che l’istanza finta sia stata utilizzata nel modo in cui ti aspettavi.
In questo articolo ho intenzione di coprire le basi della sostituzione di parti dei vostri sistemi, ma non sarò copertura fare affermazioni.
Iniziamo con un semplice esempio. Ho intenzione di creare un nuovo modulo chiamato simple.py e in quel modulo definirò una funzione chiamata simple_function che restituisce solo una stringa in questo modo:
def simple_function():
return "You have called simple_function"
Ora creerò un secondo modulo chiamato use_simple.py., In quel modulo ho intenzione di importare il pacchetto finto da unittest e importare il modulo semplice:
from unittest import mockimport simple
Avanti ho intenzione di una scrittura due funzioni, uno che chiama simple_function normalmente e uno che prende in giro la chiamata ad esso. Nota che normalmente lo faresti in un contesto di test, ma lo sto volutamente spogliando in modo da potermi concentrare sulla parte beffarda. La prima funzione:
def use_simple_function():
result = simple.simple_function()
print(result)use_simple_function()
Come puoi vedere, stampa solo il risultato di simple_function., In particolare, l’output è:
You have called simple_function
Per la seconda funzione, per mock simple_function userò il mock.decoratore di patch. Questo decoratore consente di specificare ciò che si vuole prendere in giro passando una stringa nel formato ‘pacchetto.modulo.FunctionName’., Nel nostro esempio non c’è nessun pacchetto, ma il modulo è chiamato semplice e la funzione viene chiamata simple_function, in modo che il decoratore sarà simile a:
@mock.patch('simple.simple_function')
poi, Dobbiamo scrivere la nostra seconda funzione così:
@mock.patch('simple.simple_function')
def mock_simple_function():
mi hanno chiamato mock_simple_function, ma può essere chiamato nulla. Ci manca un parametro qui perché quando decori una funzione con @mock.patch passerà un’istanza della classe MagicMock (un oggetto MagicMock) che viene utilizzato per sostituire la funzione che si sta prendendo in giro., Quindi sarà simile a questo:
@mock.patch('simple.simple_function')
def mock_simple_function(mock_simple_func):
Quindi specificando @mock.patch (‘semplice.simple_function’) Sto dicendo che voglio sostituire simple_function con l’oggetto MagicMock assegnato al parametro chiamato mock_simple_func.
Inizialmente rimpolpiamo la funzione stampando mock_simple_func e poi chiamiamolo:
Quando viene eseguito, l’output è:
<MagicMock name='simple_function'>
Che è un oggetto MagicMock che verrà chiamato invece di simple_function., È importante notare che l’attributo name dell’oggetto è la cosa che viene derisa e l’id è un numero univoco per l’oggetto.
Se ora stampiamo anche simple_function (ma non lo chiamiamo):
Che produce l’output:
<MagicMock name='simple_function'>
<MagicMock name='simple_function'>
Puoi vedere che dagli ID corrispondenti mock_simple_func è lo stesso oggetto MagicMock di simple_function.
Finora tutto bene. Abbiamo deriso simple_function. Per essere esplicito, simple_function è stato deriso perché è stato sostituito con un oggetto MagicMock.,
Aggiungiamo le linee dalla prima funzione:
Quando viene eseguito, l’output è:
Come puoi vedere dalla terza riga di output quando viene effettivamente chiamato simple_function crea un oggetto MagicMock diverso. Ricorda che dal momento che abbiamo deriso simple_function, in realtà è un oggetto MagicMock, quindi quando chiamiamo simple_function stiamo effettivamente chiamando l’oggetto MagicMock!
Per me, è qui che entra in gioco la confusione.
- Perché viene creato un nuovo oggetto MagicMock?
- E come diavolo puoi chiamare un oggetto comunque?, Normalmente ottieni “TypeError:’ * * * * ‘l’oggetto non è richiamabile” quando provi.
Facciamo una pausa per un minuto per guardare più da vicino MagicMock.
MagicMock ha magic nel suo nome perché ha implementazioni predefinite della maggior parte dei metodi magici python. Cos’è un metodo magico python che chiedi? Sono tutte funzioni Python speciali che hanno un doppio trattino basso all’inizio e alla fine del loro nome. Potete trovare un elenco di loro qui. Quello di interesse qui è _ _ call__., La chiamata di funzione è descritta come:
Chiamato quando un oggetto è definito come una funzione
Quindi, se una classe implementa questa funzione, è possibile creare un’istanza della classe, e quindi chiamare l’istanza come una funzione, cioè :
Quindi, quando MagicMock è chiamato come una funzione, l’ __call__ funzione viene chiamata. L’implementazione di MagicMock di questa funzione crea una nuova simulazione!
Quindi quando chiamiamo simple_function() viene creata e restituita una nuova simulazione. Non solo, ma abbiamo il concetto di genitore e bambino prende in giro., Quando un finto crea un altro diventa il genitore e quello appena creato il bambino. Quel bambino finto potrebbe creare altre prese in giro in modo da finire con un’ereditarietà di oggetti finti.
Punto di apprendimento 1: Quando un oggetto MagicMock viene chiamato come una funzione, per impostazione predefinita crea e restituisce un nuovo oggetto MagicMock.
Torna al nostro esempio. Finora abbiamo appena deriso simple_function ma non fatto nulla con esso se non stampare i MagicMocks che vengono creati come risultato. Diciamo che volevo cambiare ciò che è stato restituito da simple_function (), come avrei fatto?, Lo farei usando la proprietà return_value di MagicMock in questo modo (rimuoverò anche le prime due stampe poiché non sono più necessarie):
Quando viene eseguito, l’output è:
You have mocked simple_function
Come puoi vedere quando simple_function () viene ora chiamato Magicmock return_value viene restituito invece di creare un secondo oggetto MagicMock.
Punto di apprendimento 2: Per modificare ciò che viene restituito quando un oggetto MagicMock viene chiamato come una funzione, impostare return_value.
Se vuoi fare di più che modificare il valore restituito, puoi usare MagicMock.funzione side_effect., Ti permette di specificare una funzione completamente nuova che verrà chiamata al posto di quella che stai prendendo in giro. Facciamolo. In primo luogo, io definire una nuova funzione che sarà il side_effect (nota deve avere lo stesso set di parametri come la funzione beffardo):
def side_effect_function():
return "SKABLAM!"
Ora siamo in grado di definire una nuova funzione che utilizza questo tipo di effetto collaterale, come:
Tutto è lo stesso come il mock_simple_function più in alto, tranne sto impostando il side_effect di mock_simple_func invece di return_value.,
L’output è:
SKABLAM!
Un buon caso d’uso per l’utilizzo dell’effetto collaterale è se si desidera testare un flusso di errori e quindi si desidera sollevare un’eccezione nel test. Cambierò side_effect_function per generare invece un errore:
def side_effect_function():
raise FloatingPointError("A disastrous floating point error has occurred")
Ora l’output è (ho rimosso parte del traceback per chiarezza):
Traceback (most recent call last):
File "use_simple.py”, line 18, in side_effect_function
raise FloatingPointError("A disastrous floating point error has occurred”)
FloatingPointError: A disastrous floating point error has occurred
Punto di apprendimento 3: Per fare di più che cambiare il return_value di un MagicMock, imposta side_effect.
Classi di derisione
Finora abbiamo appena deriso una funzione, ma che ne dici di una classe?, Cerchiamo di definire una classe in simple.py chiamato SimpleClass con un metodo chiamato esplodere:
class SimpleClass(object):
def explode(self):
return "KABOOM!"
Ora in use_simple.py scriviamo una funzione che utilizza SimpleClass e quindi chiamare la funzione:
def use_simple_class():
inst = simple.SimpleClass()
print(inst.explode())use_simple_class()
Quando si esegue l’output è:
KABOOM!
Ora facciamo definire una seconda funzione e scegliere di prendere in giro la totalità di simple_class utilizzando @finto.patch decorator:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
Come quando si prende in giro una funzione, il @mock.,patch decorator passa un oggetto MagicMock che sostituisce la classe che stai deridendo nella funzione che sta decorando. L’oggetto MagicMock in questo caso viene assegnato all’argomento mock_class.
Per il momento stamperò semplicemente l’oggetto MagicMock e quindi eseguirò la funzione:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)mock_simple_class()
Quando esegui l’output è:
<MagicMock name='SimpleClass'>
Come con il mocking di una funzione viene creato e passato un oggetto MagicMock., Ora consente di stampare SimpleClass in modo da poter vedere che è stato deriso e sostituito con lo stesso oggetto MagicMock:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)
print(simple.SimpleClass)
L’output ora è:
<MagicMock name='SimpleClass'>
<MagicMock name='SimpleClass'>
Creiamo un’istanza di SimpleClass, ovvero chiamiamo SimpleClass (), e quindi stampiamo. Ricorda, dato che abbiamo deriso SimpleClass, ciò che effettivamente accadrà è che chiameremo l’oggetto MagicMock come una funzione:
L’output è:
Quindi, come previsto, chiamando SimpleClass() e quindi chiamando l’oggetto MagicMock come una funzione crea un nuovo oggetto MagicMock.,
Fino ad ora, prendere in giro una classe è stato molto simile a prendere in giro una funzione. Tuttavia, in realtà quando definiamo una classe creeremo oggetti usando quella classe ed è quegli oggetti il più delle volte che vogliamo davvero prendere in giro. La domanda è: come prendere in giro un’istanza creata da una classe? Bene, quando si prende in giro una classe e di conseguenza viene creato un oggetto MagicMock, se si fa riferimento a return_value su quell’oggetto MagicMock crea un nuovo oggetto MagicMock che rappresenta l’istanza della classe creata. Accidenti! Era una frase e mezzo! Questo è stato sicuramente uno dei miei ” Aha!,”momenti, ma anche scriverlo non lo rende chiaro. Stampiamo return_value in modo da poter vedere cosa intendo:
L’output è:
La terza riga viene dalla stampa dell’istanza della classe creata e la quarta riga viene dalla stampa del return_value dell’oggetto MagicMock passato alla funzione. Come puoi vedere si riferiscono allo stesso oggetto MagicMock.
Punto di apprendimento 4: Quando si prende in giro una classe, viene creato un oggetto MagicMock. Quando si crea un’istanza di quella classe viene creato un nuovo oggetto MagicMock., Quando si fa riferimento al return_value dell’oggetto MagicMock di quella classe si ottiene lo stesso oggetto MagicMock creato quando è stata creata l’istanza di quella classe.
“Ahhhhh, perché è così importante?!?!?”Ti sento piangere!
“E’ importante se si vuole prendere in giro la funzione di esplodere ” I muggito in cambio!
La prossima domanda che devi porre è: è il metodo che voglio prendere in giro un metodo di classe o un metodo di istanza? Perché il modo in cui cambi return_value del metodo sarà diverso a seconda della risposta.,
Se il metodo è un metodo di una classe, allora si potrebbe impostare il return_value in questo modo:
MagicMockObject.ClassMethodName.return_value = "A delightful return value"
Ma se si trattasse di un metodo di istanza si potrebbe fare:
MagicMockObject.return_value.InstanceMethodName.return_value = "A daring return value"
Con questo in mente, proviamo a cambiare il return_value di esplodere, che è un metodo di istanza (vado a rimuovere la maggior parte delle istruzioni di stampa per chiarezza) :
Nel codice sopra, mock_class.return_value restituisce l’oggetto MagicMock che rappresenta l’istanza di SimpleClass.
mock_class.return_value.,explode restituisce l’oggetto MagicMock che rappresenta il metodo explode dell’istanza di SimpleClass.
Quindi impostando il return_value di mock_class.return_value.explode imposta il return_value del metodo explode dell’istanza di SimpleClass.
L’output è:
BOO!
Proprio come quando si prende in giro una funzione, è possibile impostare side_effect anche per i metodi di classi e oggetti.
Spero che questo articolo ti abbia aiutato a capire un po ‘ di più. Si prega di raccomandare se ti è piaciuto e sentitevi liberi di lasciare una risposta come mi piacerebbe sentire da voi.