Cómo burlarse de una importación

144

El módulo Aincluye import Ben su parte superior. Sin embargo, bajo condiciones de prueba que me gustaría burlarse B de A(simulacro A.B) y abstenerse completamente de la importación B.

De hecho, Bno está instalado en el entorno de prueba a propósito.

Aes la unidad bajo prueba Tengo que importar Acon toda su funcionalidad. Bes el módulo que necesito burlarme. Pero ¿cómo puedo burlarse Bdentro Ay parada Ade la importación de los bienes B, si lo primero que Ahace es la importación B?

(La razón por la que B no está instalado es porque uso pypy para realizar pruebas rápidas y desafortunadamente B todavía no es compatible con pypy).

¿Como se puede hacer esto?

Jonathan
fuente

Respuestas:

134

Puede asignar a sys.modules['B']antes de importar Apara obtener lo que desea:

test.py :

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

print(A.B.__name__)

A.py :

import B

Nota B.py no existe, pero cuando se ejecuta test.pyno se devuelve ningún error e print(A.B.__name__)imprime mock_B. Aún tiene que crear un lugar mock_B.pydonde se burle Bde las funciones / variables / etc. O simplemente puede asignar un Mock()directamente:

test.py :

import sys
sys.modules['B'] = Mock()
import A
Rob Wouters
fuente
3
tenga en cuenta que Mockno parcheará algunos atributos mágicos ( __%s__) como __name__.
reclosedev
77
@reclosedev - hay Magic Mock para eso
Jonathan
2
¿Cómo deshaces esto para que la importación B vuelva a ser ImportError? Lo intenté sys.modules['B'] = Nonepero no parece funcionar.
audiodude
2
¿Cómo restablece esta importación simulada al final de la prueba, para que otros archivos de prueba de la unidad no se vean afectados por el objeto simulado?
Riya John
1
para mayor claridad, debe editar la respuesta para importar realmente mocky luego llamarmock.Mock()
nmz787
28

El builtin __import__se puede burlar con la biblioteca 'simulacro' para un mayor control:

# 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

Decir Aparece:

import B

def a():
    return B.func()

A.a()devoluciones b_mock.func()que se pueden burlar también.

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

Nota para Python 3: como se indica en el registro de cambios para 3.0 , __builtin__ahora se llama builtins:

Modificó el nombre del módulo __builtin__a builtins(eliminando los guiones bajos, agregando una 's').

El código en esta respuesta funciona bien si lo reemplaza __builtin__por builtinsPython 3.

siebz0r
fuente
1
¿Alguien ha confirmado que esto funciona? Estoy viendo que import_mockme llamen por import A, pero no por nada que importe.
Jonathon Reinhart
3
Con Python 3.4.3 consigoImportError: No module named '__builtin__'
Lucas Cimon
tienes que importar__builtin__
Aidenhjj
1
@LucasCimon, reemplace __builtin__por builtinspython3 ( docs.python.org/3/whatsnew/3.0.html?highlight=__builtin__ )
Luke Marlin
17

¿Cómo burlarse de una importación (simulacro AB)?

El módulo A incluye la importación B en su parte superior.

Fácil, simplemente simula la biblioteca en sys.modules antes de que se importe:

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

y luego, siempre y cuando Ano se base en tipos específicos de datos devueltos por los objetos de B:

import A

debería funcionar

También puedes burlarte de import A.B:

Esto funciona incluso si tiene submódulos, pero querrá burlarse de cada módulo. Digamos que tienes esto:

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

Para burlarse, simplemente haga lo siguiente antes de importar el módulo que contiene lo anterior:

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

(Mi experiencia: tenía una dependencia que funcionaba en una plataforma, Windows, pero no funcionaba en Linux, donde ejecutamos nuestras pruebas diarias. Así que necesitaba burlarme de la dependencia para nuestras pruebas. Por suerte era una caja negra, así que No necesitaba configurar mucha interacción).

Efectos secundarios burlones

Anexo: En realidad, necesitaba simular un efecto secundario que tomó algún tiempo. Así que necesitaba un método de objeto para dormir por un segundo. Eso funcionaría así:

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)

Y luego el código tarda un tiempo en ejecutarse, al igual que el método real.

Aaron Hall
fuente
7

Me doy cuenta de que llego un poco tarde a la fiesta aquí, pero aquí hay una forma algo loca de automatizar esto con la mockbiblioteca:

(aquí hay un ejemplo de uso)

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

La razón por la que esto es tan ridículamente complicado es cuando se produce una importación, Python básicamente hace esto (por ejemplo from herp.derp import foo)

  1. ¿Existe sys.modules['herp']? De lo contrario importalo. Si aun noImportError
  2. ¿Existe sys.modules['herp.derp']? De lo contrario importalo. Si aun noImportError
  3. Obtener atributo foode sys.modules['herp.derp']. MásImportError
  4. foo = sys.modules['herp.derp'].foo

Hay algunas desventajas en esta solución pirateada: si algo más se basa en otras cosas en la ruta del módulo, este tipo de problemas lo arruina. Además, esto solo funciona para cosas que se importan en línea, como

def foo():
    import herp.derp

o

def foo():
    __import__('herp.derp')
Anthony Sottile
fuente
7

La respuesta de Aaron Hall funciona para mí. Solo quiero mencionar una cosa importante,

si en A.pyti lo haces

from B.C.D import E

entonces en test.pydebe burlarse de cada módulo a lo largo del camino, de lo contrario obtendráImportError

sys.modules['B'] = mock.MagicMock()
sys.modules['B.C'] = mock.MagicMock()
sys.modules['B.C.D'] = mock.MagicMock()
Qingyi Wu
fuente
4

Encontré una buena manera de burlarse de las importaciones en Python. Aquí se encuentra la solución Zaadi de Eric que acabo de usar dentro de mi aplicación Django .

Tengo una clase SeatInterfaceque es la interfaz para la Seatclase de modelo. Entonces dentro de mi seat_interfacemódulo tengo una importación de este tipo:

from ..models import Seat

class SeatInterface(object):
    (...)

Quería crear pruebas aisladas para la SeatInterfaceclase con Seatclase burlada como FakeSeat. El problema era: cómo ejecutar las pruebas fuera de línea, donde la aplicación Django está inactiva. Tuve el siguiente error:

Configuración incorrecta: configuración solicitada BASE_DIR, pero la configuración no está configurada. Debe definir la variable de entorno DJANGO_SETTINGS_MODULE o llamar a settings.configure () antes de acceder a la configuración.

Prueba de rango 1 en 0.078s

FALLIDO (errores = 1)

La solución fue:

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

Y luego la prueba funciona mágicamente OK :)

.
Prueba de rango 1 en 0.002s

Okay

Hunter_71
fuente
3

Si haces un import ModuleB, realmente estás llamando al método incorporado __import__como:

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

Puede sobrescribir este método importando el __builtin__módulo y hacer un contenedor alrededor del __builtin__.__import__método. O podrías jugar con el NullImportergancho del impmódulo. Capturando la excepción y burlándose de su módulo / clase en el exceptbloque.

Puntero a los documentos relevantes:

docs.python.org: __import__

Acceso a las partes internas de importación con el módulo imp

Espero que esto ayude. Se le recomienda encarecidamente que entre en los perímetros más arcanos de la programación de Python y que a) comprenda de manera sólida lo que realmente quiere lograr yb) comprenda a fondo las implicaciones es importante.

Don Question
fuente