Le module A
inclut import B
à son sommet. Cependant, dans des conditions de test, je voudrais simulerB
dans A
(simuler A.B
) et s'abstenir complètement d'importer B
.
En fait, B
n'est pas installé volontairement dans l'environnement de test.
A
est l'unité à tester. Je dois importer A
avec toutes ses fonctionnalités. B
est le module que je dois me moquer. Mais comment puis-je me moquer de B
dans A
et empêcher A
d'importer le vrai B
, si la première chose que A
fait est d'import B
?
(La raison pour laquelle B n’est pas installé, c’est que j’utilise pypy pour des tests rapides. Malheureusement, B n’est pas encore compatible avec pypy.)
Comment cela pourrait-il être fait?
Vous pouvez assigner à sys.modules['B']
Avant d'importer A
pour obtenir ce que vous voulez:
test.py:
import sys
sys.modules['B'] = __import__('mock_B')
import A
print(A.B.__name__)
A.py:
import B
Remarque: B.py n'existe pas, mais lors de l'exécution de test.py
, Aucune erreur n'est renvoyée et print(A.B.__name__)
imprime mock_B
. Vous devez toujours créer un mock_B.py
Dans lequel vous vous moquez des fonctions/variables/etc réelles de B. Ou vous pouvez simplement assigner un Mock () directement:
test.py:
import sys
sys.modules['B'] = Mock()
import A
L'intégré __import__
Peut être simulé avec la bibliothèque 'simulée' pour plus de contrôle:
# 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
Dites A
ressemble à ceci:
import B
def a():
return B.func()
A.a()
retourne b_mock.func()
qui peut également être simulé.
b_mock.func.return_value = 'spam'
A.a() # returns 'spam'
Note pour Python 3: Comme indiqué dans le changelog for 3. , __builtin__
S'appelle maintenant builtins
:
Module renommé
__builtin__
Enbuiltins
(en supprimant les traits de soulignement, en ajoutant un "s").
Le code de cette réponse fonctionne correctement si vous remplacez __builtin__
Par builtins
pour Python 3.
Comment se moquer d'une importation, (maquette A.B)?
Le module A inclut l'importation B en haut.
Facile, modifiez simplement la bibliothèque dans sys.modules avant de l'importer:
if wrong_platform():
sys.modules['B'] = mock.MagicMock()
et ensuite, tant que A
ne repose pas sur des types spécifiques de données renvoyées par les objets de B:
import A
devrait juste travailler.
import A.B
:Cela fonctionne même si vous avez des sous-modules, mais vous voudrez vous moquer de chaque module. Dis que tu as ceci:
from foo import This, That, andTheOtherThing
from foo.bar import Yada, YadaYada
from foo.baz import Blah, getBlah, boink
Pour vous moquer, procédez simplement comme suit avant que le module contenant ce qui précède soit importé:
sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
(Mon expérience: j'avais une dépendance qui fonctionnait sur une plate-forme, Windows, mais pas sous Linux, où nous effectuons nos tests quotidiens. Il fallait donc que je me moque de la dépendance de nos tests. Heureusement, il s'agissait d'une boîte noire. Je n'ai pas eu besoin de mettre en place beaucoup d'interaction.)
Addendum: En fait, je devais simuler un effet secondaire qui prenait du temps. J'ai donc eu besoin de la méthode d'un objet pour dormir une seconde. Cela fonctionnerait comme ceci:
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)
Et puis le code prend du temps à s'exécuter, tout comme la vraie méthode.
Je me rends compte que je suis un peu en retard pour la fête ici, mais voici une manière un peu folle d'automatiser ceci avec la bibliothèque mock
:
(voici un exemple d'utilisation)
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 raison pour laquelle c'est si ridiculement compliqué, c'est quand une importation a lieu python le fait fondamentalement (par exemple, from herp.derp import foo
)
sys.modules['herp']
existe? Sinon importez-le. Si toujours pas ImportError
sys.modules['herp.derp']
existe? Sinon importez-le. Si toujours pas ImportError
foo
de sys.modules['herp.derp']
. Sinon ImportError
foo = sys.modules['herp.derp'].foo
Cette solution simplifiée présente certains inconvénients: si quelque chose repose sur d’autres éléments dans le chemin du module, ce type de problème est résolu. De plus, ceci niquement fonctionne pour les éléments importés en ligne tels que
def foo():
import herp.derp
ou
def foo():
__import__('herp.derp')
Si vous faites un import ModuleB
vous appelez vraiment la méthode intégrée __import__
comme:
ModuleB = __import__('ModuleB', globals(), locals(), [], -1)
Vous pouvez écraser cette méthode en important le fichier __builtin__
module et faire un wrapper autour du __builtin__.__import__
méthode. Ou vous pouvez jouer avec le hook NullImporter
du module imp
. Attraper l'exception et simuler votre module/classe dans le bloc except
-.
Pointeur vers les documents pertinents:
Accès aux internes d'importation avec le module imp
J'espère que ça aide. Soyez [~ # ~] vivement [~ # ~] avisé que vous entrez dans les périmètres les plus obscurs de python programmation et qu’une) bonne compréhension de ce que vous voulez vraiment réaliser et b) une compréhension approfondie des implications est importante.
J'ai trouvé un bon moyen de se moquer des importations en Python. C'est la solution Zaadi d'Eric trouvée ici que je viens d'utiliser dans mon Django application.
J'ai la classe SeatInterface
qui est l'interface avec la classe de modèle Seat
. Donc, dans mon seat_interface
module j'ai une telle importation:
from ..models import Seat
class SeatInterface(object):
(...)
Je voulais créer des tests isolés pour SeatInterface
class avec Seat
class simulée comme FakeSeat
. Le problème était de savoir comment exécuter des tests hors ligne, où Django est en panne. J'avais l'erreur ci-dessous:
ImproperlyConfigured: Paramètre demandé BASE_DIR, mais les paramètres ne sont pas configurés. Vous devez définir la variable d'environnement Django_SETTINGS_MODULE ou appeler settings.configure () avant d'accéder aux paramètres.
A couru 1 test en 0.078s
FAILED (errors = 1)
La solution était:
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
Et puis test fonctionne comme par magie OK :)
.
Ran 1 test en 0.002sD'accord
La réponse d'Aaron Hall fonctionne pour moi. Je veux juste mentionner une chose importante,
si dans A.py
tu fais
from B.C.D import E
puis dans test.py
vous devez vous moquer de chaque module le long du chemin, sinon vous obtenez ImportError
sys.moduels['B'] = mock.MagicMock()
sys.moduels['B.C'] = mock.MagicMock()
sys.moduels['B.C.D'] = mock.MagicMock()