web-dev-qa-db-fra.com

Comment se moquer d'une importation

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?

119
Jonathan

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
115
Rob Wouters

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__ En builtins (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.

20
siebz0r

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.

Vous pouvez aussi vous moquer de 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.)

Effets secondaires moqueurs

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.

13
Aaron Hall

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)

  1. Est-ce que sys.modules['herp'] existe? Sinon importez-le. Si toujours pas ImportError
  2. Est-ce que sys.modules['herp.derp'] existe? Sinon importez-le. Si toujours pas ImportError
  3. Récupère l'attribut foo de sys.modules['herp.derp']. Sinon ImportError
  4. 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')
7
Anthony Sottile

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:

docs.python.org: __import__

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.

3
Don Question

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

D'accord

3
Hunter_71

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()
0
Qingyi Wu