unittest mock python mocking python-import

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.002s

DE 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 )

  1. ¿Existe sys.modules[''herp''] ? De lo contrario importarlo. Si aún no es ImportError
  2. ¿Existe sys.modules[''herp.derp''] ? De lo contrario importarlo. Si aún no es ImportError
  3. Obtenga el atributo foo de sys.modules[''herp.derp''] . Else ImportError
  4. 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:

docs.python.org: __import__

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.