2012-06-21 19 views
15
class HelloWorld(object): 
    def say_it(self): 
     return 'Hello I am Hello World' 

def i_call_hello_world(hw_obj): 
    print 'here... check type: %s' %type(HelloWorld) 
    if isinstance(hw_obj, HelloWorld): 
     print hw_obj.say_it() 

from mock import patch, MagicMock 
import unittest 

class TestInstance(unittest.TestCase): 
    @patch('__main__.HelloWorld', spec=HelloWorld) 
    def test_mock(self,MK): 
     print type(MK) 
     MK.say_it.return_value = 'I am fake' 
     v = i_call_hello_world(MK) 
     print v 

if __name__ == '__main__': 
    c = HelloWorld() 
    i_call_hello_world(c) 
    print isinstance(c, HelloWorld) 
    unittest.main() 

Ecco la tracebackisinstance e beffardo

here... check type: <type 'type'> 
Hello I am Hello World 
True 
<class 'mock.MagicMock'> 
here... check type: <class 'mock.MagicMock'> 
E 
====================================================================== 
ERROR: test_mock (__main__.TestInstance) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "/usr/local/lib/python2.7/dist-packages/mock.py", line 1224, in patched 
    return func(*args, **keywargs) 
    File "t.py", line 18, in test_mock 
    v = i_call_hello_world(MK) 
    File "t.py", line 7, in i_call_hello_world 
    if isinstance(hw_obj, HelloWorld): 
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types 

---------------------------------------------------------------------- 
Ran 1 test in 0.002s 

Q1. Perché viene generato questo errore? Sono <class type='MagicMock>

Q2. Come faccio a mettere in pausa il mocking in modo che la prima riga passi se l'errore è stato risolto?

Dal doc

Normalmente il classe attributo di un oggetto tornerà suo tipo. Per un oggetto fittizio con una specifica classe restituisce invece la classe di specifica . Questo permette oggetti mock a superare test isinstance per l'oggetto che sostituiscono/mascherata da:

mock = Mock(spec=3) 
isinstance(mock, int) 
True 

Grazie

+2

Ora sai perché l'uso di 'isinstance' è scoraggiato. –

+1

@MarkRansom Sì, è il male. Ma qual è la migliore pratica per garantire che l'interfaccia che passiamo sia CORRETTA? neanche "hasattr" sembra risolvere il problema. Due oggetti potrebbero avere gli stessi nomi di metodo e usare l'oggetto sbagliato faranno passare il test, credo? Immagino che l'attenzione della domanda si sia spostata! Ahh. – CppLearner

+0

Questo è il punto: una delle molte cose carine di Python è che consente "Duck Typing" in cui non ti interessa il tipo esatto di un oggetto finché fa quello che vuoi. Potrebbe essere necessario fare attenzione alla denominazione dei metodi per assicurarsi di non definire lo stesso nome con due significati diversi, ma alla fine offre una grande flessibilità al codice. È una caratteristica, non un bug. –

risposta

-4

Non utilizzare isinstance, invece verificare l'esistenza del metodo say_it. Se il metodo esiste, lo chiamano:

if hasattr(hw_obj, 'say_it'): 
    print hw_obj.say_it() 

Questo è un disegno meglio comunque: basandosi su informazioni sul tipo è molto più fragile.

+0

Grazie. Ma la prima domanda sarebbe davvero perché sta generando quell'errore? E due, adottando questo cambiamento, se due objs hanno lo stesso nome di metodo, il test passerà, giusto? – CppLearner

+1

Non so perché l'errore è stato generato. Non importa, presto non userai 'isinstance' :) E sì, ora puoi passare in qualsiasi oggetto che ha il metodo, e si comporta" correttamente ", e il test passerà. –

+0

Grazie. Smetterò di usare isinstance dopo aver letto il fallback principale. BUt ancora ... se 'MyMomKitchenObject' ha' say_it' e il programmatore lo usa come input della funzione ... il test passerà ancora, vero? Quindi, come dovrei verificare che la mia unittest funzioni effettivamente? o come dovrei determinare la sua correttezza nel mio codice? Proprio come il test di integrazione, due objs possono avere il 99% della stessa interfaccia, e il sistema sotto test non usa mai quell'1% diverso, e il test continua a passare, il sistema funzionerà senza problemi. – CppLearner

23

IMHO questa è una buona domanda e dicendo "non utilizzare isinstance, utilizzare la digitazione anatra invece" è una cattiva risposta. La battitura delle anatre è ottima, ma non una pallottola d'argento. A volte è necessario isinstance, anche se non è pitonico. Ad esempio, se lavori con alcune librerie o codici legacy che non sono pythonic, devi giocare con isinstance. È solo il mondo reale e il mock è stato progettato per adattarsi a questo tipo di lavoro.

Nel codice il grande errore è quando si scrive:

@patch('__main__.HelloWorld', spec=HelloWorld) 
def test_mock(self,MK): 

Da patch documentation leggiamo (sottolineare è mio):

All'interno del corpo della funzione o con la dichiarazione, l'obiettivo viene aggiornato con un nuovo oggetto .

Ciò significa che quando si applica una patch la classe di oggettiHelloWorldil riferimento a HelloWorld sarà sostituito da un oggetto MagicMock per il contesto della funzione test_mock().

Poi, quando i_call_hello_world() viene eseguito in if isinstance(hw_obj, HelloWorld):HelloWorld è un oggetto MagicMock() e non una classe (come l'errore suggerisce).

Questo comportamento è dovuto al fatto che come un effetto collaterale del patching di un riferimento di classe il secondo argomento di isinstance(hw_obj, HelloWorld) diventa un oggetto (un'istanza MagicMock). Questo non è né uno class né uno type.Un semplice esperimento per capire questo comportamento è di modificare i_call_hello_world() come segue:

HelloWorld_cache = HelloWorld 

def i_call_hello_world(hw_obj): 
    print 'here... check type: %s' %type(HelloWorld_cache) 
    if isinstance(hw_obj, HelloWorld_cache): 
     print hw_obj.say_it() 

L'errore scompare perché il riferimento originale per HelloWorld classe viene salvato nella HelloWorld_cache quando si carica il modulo. Quando viene applicata la patch, cambierà solo HelloWorld e non HelloWorld_cache.

Sfortunatamente, l'esperimento precedente non ci dà modo di giocare con casi come il tuo perché non è possibile modificare la libreria o il codice legacy per introdurre un trucco come questo. Inoltre, questi sono quel tipo di trucco che non vorremmo mai vedere nel nostro codice.

La buona notizia è che si può fare qualcosa, ma non si può solo patch il riferimento HelloWord nel modulo in cui devi isinstace(o,HelloWord) codice per verificare. Il modo migliore dipende dal caso reale che devi risolvere. Nel tuo esempio puoi semplicemente creare un Mock da utilizzare come oggetto HelloWorld, usare l'argomento spec per vestirlo come istanza HelloWorld e superare il test isinstance. Questo è esattamente uno degli scopi per cui è progettato spec. Il test dovranno essere scritte in questo modo:

def test_mock(self): 
    MK = MagicMock(spec=HelloWorld) #The hw_obj passed to i_call_hello_world 
    print type(MK) 
    MK.say_it.return_value = 'I am fake' 
    v = i_call_hello_world(MK) 
    print v 

E l'uscita di solo una parte unittest è

<class 'mock.MagicMock'> 
here... check type: <type 'type'> 
I am fake 
None 
+0

Questa affermazione non è corretta: "HelloWorld è un oggetto Mock() e non una classe (come l'errore suggerito)." Se rilevi il TypeError originale e il debug, vedrai che l'esecuzione di 'type (HelloWorld)' restituirà anche un . –

+0

@seanazlin Lo controllerò più tardi. Ma perché l'errore dice che HelloWord non è una classe o un tipo? Comunque grazie per il tuo feedback. –

+0

@SeanAzlin Digitare sulla propria console python le seguenti istruzioni: 'type (MagicMock())', 'type (MagicMock)', 'type (object())', 'type (oggetto)'. Dopodiché spero capirai che quello che ho scritto è corretto. Ad ogni modo se il tuo commento su I scrisse 'Mock' invece di' MagicMock', penso che sia sbagliato ma non un grosso problema ... È solo un dettaglio che aggiusterò. Prestare maggiore attenzione quando si utilizza downvote. La mia risposta è corretta ed è l'unica che copre la domanda originale senza dire * Ehi ragazzo! Non farlo ". –

0

Sono stato alle prese con questo me stesso ultimamente durante la scrittura di alcuni test di unità. Una possibile soluzione è quella di non provare effettivamente a prendere in giro l'intera classe HelloWorld, ma invece di prendere in giro i metodi della classe che vengono chiamati dal codice che si sta testando. Ad esempio, qualcosa come questo dovrebbe funzionare:

class HelloWorld(object): 
    def say_it(self): 
     return 'Hello I am Hello World' 

def i_call_hello_world(hw_obj): 
    if isinstance(hw_obj, HelloWorld): 
     return hw_obj.say_it() 

from mock import patch, MagicMock 
import unittest 

class TestInstance(unittest.TestCase): 
    @patch.object(HelloWorld, 'say_it') 
    def test_mock(self, mocked_say_it): 
     mocked_say_it.return_value = 'I am fake' 
     v = i_call_hello_world(HelloWorld()) 
     self.assertEquals(v, 'I am fake') 
+0

Che dire se ** hai ** bisogno di deridere la classe perché molti metodi interni non possono essere utilizzati nel contesto di test? La domanda che parla di * Instance and Mocking * e non * Mocking di un metodo * e dove sono le risposte a Q1 e Q2? Non metto la tua risposta in negativo come hai fatto tu, ma forse devi prestare maggiore attenzione alle domande prima di rispondere alle domande. Per quanto riguarda la tua risposta da un punto di vista tecnico, perché usare 'patch.object' non ne hai veramente bisogno. Puoi usare '@patch ('__ main __. HelloWorld.say_it', return_value = 'I am fake')' che è più conciso e più semplice da leggere. –

+0

@ Micheled'Amico Punti fini. Quello che sto proponendo è solo una potenziale soluzione (o forse un work-around è un termine migliore) per alcune situazioni, come quando si sta testando un'unità una funzione che usa isinstance() e che chiama il metodo di una classe che puo ' essere deriso a causa di questo problema Penso che il work-around potrebbe essere più allettante per alcuni rispetto alle soluzioni suggerite in precedenza. Ha funzionato per me nella mia situazione. –

+0

Quello che non si può fare quando si ha la chiamata a 'isinstance()' è solo una patch della classe, ma è possibile usare una simulazione per l'oggetto. BTW: fai attenzione quando usi 'patch.object': usalo solo quando ne hai veramente bisogno o molto tempo non capirai perché la tua patch non funzioni. (Considerare di rimuovere il downvote dalla mia risposta perché la tua osservazione è sbagliata) –

2

Michele d'Amico prevede l'correct answer a mio avviso, e lo consiglio vivamente di leggerlo. Ma mi c'è voluto un po 'di Grok e, come sono sicuro che tornerò a questa domanda in futuro, ho pensato che un esempio di codice minimo potrebbe aiutare a chiarire la soluzione e fornire un riferimento rapido:

from mock import patch, mock 

class Foo(object): pass 

# Cache the Foo class so it will be available for isinstance assert. 
FooCache = Foo 

with patch('__main__.Foo', spec=Foo): 
    foo = Foo() 
    assert isinstance(foo, FooCache) 
    assert isinstance(foo, mock.mock.NonCallableMagicMock) 

    # This will cause error from question: 
    # TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types 
    assert isinstance(foo, Foo) 
Problemi correlati