python - mock - Cómo burlarse de una importación
python unittest (6)
Cómo burlarse de una importación, (falsa AB)?
El Módulo A incluye la importación B en su parte superior.
Fácil, solo burla de la biblioteca en sys.modules antes de que se importe:
if wrong_platform():
sys.modules[''B''] = mock.MagicMock()
y luego, siempre que A
no confíe en tipos específicos de datos devueltos por los objetos de B:
import A
debería funcionar
También puedes simularte import AB
:
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 simular, 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 funciona en una plataforma, Windows, pero no funcionaba en Linux, donde realizamos nuestras pruebas diarias. Por lo tanto, necesitaba burlarme de la dependencia de nuestras pruebas. Afortunadamente era una caja negra, por lo que No necesité establecer mucha interacción.)
Efectos secundarios burlones
Adición: De hecho, necesitaba simular un efecto secundario que demoró un poco. Así que necesitaba el método de un 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 algo de tiempo en ejecutarse, al igual que el método real.
El Módulo A
incluye la import B
en su parte superior. Sin embargo, en condiciones de prueba me gustaría mock B
en A
(falsa AB
) y me abstengo por completo de importar B
De hecho, B
no está instalado en el entorno de prueba a propósito.
A es la unidad bajo prueba. Tengo que importar A con toda su funcionalidad. B es el módulo que necesito para burlarme. Pero, ¿cómo puedo simular B dentro de A y evitar que A importe la B real, si lo primero que A hace es importar B?
(El motivo por el que B no está instalado es que utilizo pypy para realizar pruebas rápidas y, lamentablemente, B aún no es compatible con pypy).
¿Como se puede hacer esto?
El __import__
puede ser burlado con la biblioteca ''simulada'' para tener más 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
Diga A
ve así:
import B
def a():
return B.func()
Aa()
devuelve b_mock.func()
que también se puede burlar.
b_mock.func.return_value = ''spam''
A.a() # returns ''spam''
Encontré buena manera de burlarme de las importaciones en Python. here encuentra la solución de Zaadi de Eric que acabo de usar dentro de mi aplicación Django .
Tengo la clase SeatInterface
que es la interfaz de la clase de modelo Seat
. Así que dentro de mi módulo seat_interface
tengo tal importación:
from ..models import Seat
class SeatInterface(object):
(...)
Quería crear pruebas aisladas para la clase de SeatInterface
con la clase de Seat
FakeSeat
como FakeSeat
. El problema era cómo ejecutar las pruebas fuera de línea, donde la aplicación Django no funciona. Tuve el siguiente error:
Configurado incorrectamente: Se solicitó la configuración de BASE_DIR, pero la configuración no está configurada. Debe definir la variable de entorno DJANGO_SETTINGS_MODULE o invocar settings.configure () antes de acceder a la configuración.
Prueba de Ran 1 en 0.078s
FAILED (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 mágicamente funciona bien :)
.
Prueba de Ran 1 en 0.002sDE ACUERDO
Me doy cuenta de que llego un poco tarde a la fiesta aquí, pero esta es una manera un poco loca de automatizar esto con la biblioteca mock
:
(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 cual esto es tan ridículamente complicado es que cuando ocurre una importación, python básicamente hace esto (por ejemplo, from herp.derp import foo
)
- ¿Existe
sys.modules[''herp'']
? De lo contrario importarlo. Si aún no esImportError
- ¿Existe
sys.modules[''herp.derp'']
? De lo contrario importarlo. Si aún no esImportError
- Obtenga el atributo
foo
desys.modules[''herp.derp'']
. ElseImportError
-
foo = sys.modules[''herp.derp''].foo
Hay algunas desventajas de esta solución pirateada: si algo más se basa en otras cosas en la ruta del módulo, este tipo de problema se tuerce. 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'')
Puede asignar sys.modules[''B'']
antes de importar A
para 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 al ejecutar test.py
no se devuelve ningún error e print(AB__name__)
imprime mock_B
. Aún debe crear un mock_B.py
donde se mock_B.py
de las funciones / variables reales de B, etc. O simplemente puede asignar un Mock () directamente:
test.py :
import sys
sys.modules[''B''] = Mock()
import A
Si realiza un import ModuleB
, realmente está llamando al método __import__
como:
ModuleB = __import__(''ModuleB'', globals(), locals(), [], -1)
Puede sobrescribir este método importando el módulo __builtin__
y crear un contenedor alrededor del método __builtin__.__import__
. O podría jugar con el gancho NullImporter
del módulo imp
. Captura la excepción y simula tu módulo / clase en el bloque except
-block.
Puntero a los documentos relevantes:
Accediendo a las importaciones internas con el Módulo imp
Espero que esto ayude. Que se le aconseje ALTAMENTE que ingrese a los perímetros más arcanos de la programación de Python y que a) comprenda a la perfección lo que realmente desea lograr yb) que sea importante comprender bien las implicaciones.