Come deridere un'importazione


144

Il modulo Ainclude import Bnella parte superiore. Tuttavia in condizioni di prova mi piacerebbe prendere in giro B in A(finto A.B) e completamente astenersi da importare B.

In effetti, Bnon è installato di proposito nell'ambiente di test.

Aè l'unità sottoposta a test. Devo importare Acon tutte le sue funzionalità. Bè il modulo che devo prendere in giro. Ma come posso prendere in giro Ball'interno Ae l'arresto Adi importare il vero B, se la prima cosa che Afa è di importazione B?

(Il motivo per cui B non è installato è che uso pypy per test rapidi e sfortunatamente B non è ancora compatibile con pypy.)

Come si può fare?

Risposte:


134

È possibile assegnare sys.modules['B']prima di importare Aper ottenere ciò che si desidera:

test.py :

import sys
sys.modules['B'] = __import__('mock_B')
import A

print(A.B.__name__)

A.py :

import B

Nota B.py non esiste, ma durante l'esecuzione test.pynon viene restituito alcun errore e viene print(A.B.__name__)stampato mock_B. Devi ancora creare un punto in mock_B.pycui deridi Ble effettive funzioni / variabili / ecc. Oppure puoi semplicemente assegnare un Mock()direttamente:

test.py :

import sys
sys.modules['B'] = Mock()
import A

3
tieni presente che Mocknon correggerà alcuni attributi magici ( __%s__) come __name__.
reclosedev

7
@reclosedev - c'è Magic Mock per questo
Jonathan il

2
Come si annulla ciò in modo che l'importazione B sia di nuovo un ImportError? Ci ho provato sys.modules['B'] = Nonema non sembra funzionare.
audiodude

2
Come resettate questa importazione simulata alla fine del test, in modo che altri file di test unitari non vengano influenzati dall'oggetto deriso?
Riya John

1
per chiarezza, è necessario modificare la risposta per importare effettivamente mocke quindi chiamaremock.Mock()
nmz787

28

L'integrato __import__può essere deriso con la libreria 'mock' per un maggiore controllo:

# Store original __import__
orig_import = __import__
# This will be the B module
b_mock = mock.Mock()

def import_mock(name, *args):
    if name == 'B':
        return b_mock
    return orig_import(name, *args)

with mock.patch('__builtin__.__import__', side_effect=import_mock):
    import A

Say Aassomiglia a:

import B

def a():
    return B.func()

A.a()ritorni b_mock.func()che possono essere derisi anche.

b_mock.func.return_value = 'spam'
A.a()  # returns 'spam'

Nota per Python 3: come indicato nel log delle modifiche per 3.0 , __builtin__ora è chiamato builtins:

Modulo rinominato __builtin__in builtins(rimuovendo i trattini bassi, aggiungendo una 's').

Il codice in questa risposta funziona bene se lo sostituisci __builtin__con builtinsPython 3.


1
Qualcuno ha confermato che funziona? Vedo essere import_mockchiamato per il import A, ma non per tutto ciò che importa.
Jonathon Reinhart,

3
Con Python 3.4.3 ottengoImportError: No module named '__builtin__'
Lucas Cimon il

devi importare__builtin__
Aidenhjj

1
@LucasCimon, sostituisci __builtin__con builtinsfor python3 ( docs.python.org/3/whatsnew/3.0.html?highlight=__builtin__ )
Luke Marlin

17

Come deridere un'importazione, (mock AB)?

Il modulo A include l'importazione B nella parte superiore.

Semplice, prendi in giro la libreria in sys.modules prima che venga importata:

if wrong_platform():
    sys.modules['B'] = mock.MagicMock()

e quindi, purché Anon si basi su tipi specifici di dati restituiti dagli oggetti di B:

import A

dovrebbe funzionare.

Puoi anche deridere import A.B:

Funziona anche se hai sottomoduli, ma ti consigliamo di prendere in giro ogni modulo. Di 'che hai questo:

from foo import This, That, andTheOtherThing
from foo.bar import Yada, YadaYada
from foo.baz import Blah, getBlah, boink

Per deridere, fai semplicemente quanto segue prima di importare il modulo che contiene quanto sopra:

sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()

(La mia esperienza: ho avuto una dipendenza che funziona su una piattaforma, Windows, ma non ha funzionato su Linux, dove abbiamo eseguito i nostri test giornalieri. Quindi ho dovuto deridere la dipendenza per i nostri test. Fortunatamente era una scatola nera, quindi Non ho avuto bisogno di creare molta interazione.)

Effetti collaterali beffardi

Addendum: In realtà, avevo bisogno di simulare un effetto collaterale che ha richiesto del tempo. Quindi avevo bisogno del metodo di un oggetto per dormire per un secondo. Funzionerebbe così:

sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
# setup the side-effect:
from time import sleep

def sleep_one(*args): 
    sleep(1)

# this gives us the mock objects that will be used
from foo.bar import MyObject 
my_instance = MyObject()
# mock the method!
my_instance.method_that_takes_time = mock.MagicMock(side_effect=sleep_one)

E quindi l'esecuzione del codice richiede del tempo, proprio come il metodo reale.


7

Mi rendo conto che sono un po 'in ritardo alla festa qui, ma ecco un modo un po' folle per automatizzare questo con la mockbiblioteca:

(ecco un esempio di utilizzo)

import contextlib
import collections
import mock
import sys

def fake_module(**args):
    return (collections.namedtuple('module', args.keys())(**args))

def get_patch_dict(dotted_module_path, module):
    patch_dict = {}
    module_splits = dotted_module_path.split('.')

    # Add our module to the patch dict
    patch_dict[dotted_module_path] = module

    # We add the rest of the fake modules in backwards
    while module_splits:
        # This adds the next level up into the patch dict which is a fake
        # module that points at the next level down
        patch_dict['.'.join(module_splits[:-1])] = fake_module(
            **{module_splits[-1]: patch_dict['.'.join(module_splits)]}
        )
        module_splits = module_splits[:-1]

    return patch_dict

with mock.patch.dict(
    sys.modules,
    get_patch_dict('herp.derp', fake_module(foo='bar'))
):
    import herp.derp
    # prints bar
    print herp.derp.foo

Il motivo per cui questo è così ridicolmente complicato è quando si verifica un'importazione, in pratica Python lo fa (prendiamo ad esempio from herp.derp import foo)

  1. Esiste sys.modules['herp']? Altrimenti importalo. Se ancora noImportError
  2. Esiste sys.modules['herp.derp']? Altrimenti importalo. Se ancora noImportError
  3. Ottieni l'attributo foodi sys.modules['herp.derp']. AltroImportError
  4. foo = sys.modules['herp.derp'].foo

Ci sono alcuni aspetti negativi di questa soluzione compromessa: se qualcos'altro si basa su altre cose nel percorso del modulo, questo tipo di problemi. Anche questo funziona solo per cose che vengono importate in linea come

def foo():
    import herp.derp

o

def foo():
    __import__('herp.derp')

7

La risposta di Aaron Hall funziona per me. Voglio solo menzionare una cosa importante,

se in A.pyte lo fai

from B.C.D import E

allora dentro test.pydevi deridere ogni modulo lungo il percorso, altrimenti otterraiImportError

sys.modules['B'] = mock.MagicMock()
sys.modules['B.C'] = mock.MagicMock()
sys.modules['B.C.D'] = mock.MagicMock()

4

Ho trovato il modo migliore per deridere le importazioni in Python. È la soluzione Zaadi di Eric trovata qui che uso solo nella mia applicazione Django .

Ho una classe SeatInterfaceche è un'interfaccia per la Seatclasse del modello. Quindi nel mio seat_interfacemodulo ho una tale importazione:

from ..models import Seat

class SeatInterface(object):
    (...)

Volevo creare test isolati per SeatInterfaceclasse con Seatclasse derisa come FakeSeat. Il problema era: come eseguire i test offline, dove l'applicazione Django non funziona. Ho avuto sotto l'errore:

ImproprlyConfigured: impostazione richiesta BASE_DIR, ma le impostazioni non sono configurate. Devi definire la variabile di ambiente DJANGO_SETTINGS_MODULE o chiamare settings.configure () prima di accedere alle impostazioni.

Ho eseguito 1 test in 0,078 secondi

FAILED (errori = 1)

La soluzione era:

import unittest
from mock import MagicMock, patch

class FakeSeat(object):
    pass

class TestSeatInterface(unittest.TestCase):

    def setUp(self):
        models_mock = MagicMock()
        models_mock.Seat.return_value = FakeSeat
        modules = {'app.app.models': models_mock}
        patch.dict('sys.modules', modules).start()

    def test1(self):
        from app.app.models_interface.seat_interface import SeatInterface

E quindi il test funziona magicamente OK :)

.
Ho eseguito 1 test in 0,002 secondi

ok


3

Se lo fai, import ModuleBstai davvero chiamando il metodo incorporato __import__come:

ModuleB = __import__('ModuleB', globals(), locals(), [], -1)

È possibile sovrascrivere questo metodo importando il __builtin__modulo e creare un wrapper attorno al __builtin__.__import__metodo. Oppure potresti giocare con il NullImportergancio dal impmodulo. Cattura l'eccezione e deride il tuo modulo / classe nel exceptblocco.

Puntatore ai documenti pertinenti:

docs.python.org: __import__

Accesso a Import internals con il modulo imp

Spero che aiuti. Siate vivamente consigliati di entrare nei perimetri più arcani della programmazione di Python e che a) una solida comprensione di ciò che volete veramente raggiungere eb) una comprensione approfondita delle implicazioni è importante.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.