190 votes

Comment simuler une importation

Module A comprend import B à son sommet. Cependant, dans des conditions de test, j'aimerais simuler B en A (moquerie A.B ) et s'abstenir totalement d'importer B .

En fait, B n'est pas installé dans l'environnement de test à dessein.

A est l'unité testée. Je dois importer A avec toutes ses fonctionnalités. B est le module que je dois simuler. Mais comment puis-je simuler B sur A et arrêter A d'importer le vrai B si la première chose A fait est d'importer B ?

(La raison pour laquelle B n'est pas installé est que j'utilise pypy pour des tests rapides et malheureusement B n'est pas encore compatible avec pypy).

Comment cela pourrait-il être fait ?

167voto

Rob Wouters Points 6654

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

Notez que B.py n'existe pas, mais en exécutant 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 où vous vous moquez B Les fonctions/variables/etc. réels de l'utilisateur. Ou vous pouvez simplement attribuer un Mock() directement :

test.py :

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

3 votes

Gardez à l'esprit que Mock ne corrigera pas certains attributs magiques ( __%s__ ) comme __name__ .

10 votes

@reclosedev - il y a Maquette magique pour cela

4 votes

Comment défaire cela pour que l'importation B soit à nouveau une ImportError ? J'ai essayé sys.modules['B'] = None mais ça ne semble pas fonctionner.

37voto

siebz0r Points 3960

La fonction intégrée __import__ peuvent être simulés à l'aide de la bibliothèque "mock" pour un meilleur 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 :

import B

def a():
    return B.func()

A.a() renvoie à b_mock.func() qui peut également faire l'objet de moqueries.

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

Note pour Python 3 : Comme indiqué dans le journal des changements pour 3.0 , __builtin__ s'appelle désormais builtins :

Module renommé __builtin__ a builtins (en supprimant les traits de soulignement et en ajoutant un "s").

Le code dans cette réponse fonctionne bien si vous remplacez __builtin__ par builtins pour Python 3.

1 votes

Quelqu'un a-t-il confirmé que cela fonctionne ? Je vois import_mock sont appelés pour le import A mais pas pour ce qu'il importe.

3 votes

Avec Python 3.4.3 j'obtiens ImportError: No module named '__builtin__'

0 votes

Vous devez importer __builtin__

20voto

Aaron Hall Points 7381

Comment simuler une importation, (simuler A.B) ?

Le module A comprend l'importation B à son sommet.

C'est facile, il suffit de simuler la bibliothèque dans sys.modules avant qu'elle ne soit importée :

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

et ensuite, tant que A ne dépend pas de types spécifiques de données renvoyées par les objets de B :

import A

devrait fonctionner.

Vous pouvez également simuler import A.B :

Cette méthode fonctionne même si vous avez des sous-modules, mais vous devrez simuler chaque module. Disons que vous avez ceci :

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

Pour simuler, il suffit de faire ce qui suit avant d'importer le module qui contient ce qui précède :

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 plateforme, Windows, mais qui ne fonctionnait pas sur Linux, où nous effectuons nos tests quotidiens. J'avais donc besoin de simuler la dépendance pour nos tests. Heureusement, c'était une boîte noire, donc je n'ai pas eu besoin de mettre en place beaucoup d'interaction).

Se moquer des effets secondaires

Addendum : En fait, j'avais besoin de simuler un effet secondaire qui prenait du temps. J'avais donc besoin que la méthode d'un objet dorme pendant 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 un certain temps pour s'exécuter, tout comme la vraie méthode.

13voto

Qingyi Wu Points 91

La réponse d'Aaron Hall me convient. Je veux juste mentionner une chose importante,

si en A.py vous le faites

from B.C.D import E

alors dans test.py vous devez vous moquer de chaque module le long du chemin, sinon vous obtenez ImportError

sys.modules['B'] = mock.MagicMock()
sys.modules['B.C'] = mock.MagicMock()
sys.modules['B.C.D'] = mock.MagicMock()

8voto

Anthony Sottile Points 3629

Je me rends compte que je suis un peu en retard sur la fête ici, mais voici une façon un peu folle d'automatiser cela avec la fonction mock bibliothèque :

(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 cela est si ridiculement compliqué est que lorsqu'un import se produit, python fait essentiellement ceci (prenez par exemple from herp.derp import foo )

  1. Fait sys.modules['herp'] exister ? Sinon, importez-le. Si ce n'est toujours pas le cas ImportError
  2. Fait sys.modules['herp.derp'] existe ? Sinon, importez-le. Si ce n'est toujours pas le cas ImportError
  3. Obtenir l'attribut foo de sys.modules['herp.derp'] . Else ImportError
  4. foo = sys.modules['herp.derp'].foo

Cette solution bricolée présente quelques inconvénients : Si quelque chose d'autre dépend d'un autre élément dans le chemin du module, cela peut tout gâcher. De plus, cette uniquement fonctionne pour les éléments qui sont importés en ligne tels que

def foo():
    import herp.derp

ou

def foo():
    __import__('herp.derp')

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X