110 votes

Se moquer des fonctions en utilisant Python Mock

Je suis en train d'essayer de simuler une fonction (qui retourne un contenu externe) en utilisant le module mock de python.

J'ai des difficultés à simuler des fonctions qui sont importées dans un module.

Par exemple, dans util.py j'ai

def get_content():
  return "chose"

Je veux simuler util.get_content de sorte qu'elle renvoie autre chose.

Je fais ceci :

util.get_content=Mock(return_value="chose simulée")

Si get_content est invoquée à l'intérieur d'un autre module, il semble jamais renvoyer l'objet simulé. Est-ce que j'oublie quelque chose en terme d'utilisation de Mock ?

Notez que si j'invoque ce qui suit, les choses fonctionnent correctement :

>>> util.get_content=Mock(return_value="chose simulée")
>>> util.get_content()
"chose simulée"

Cependant, si get_content est appelée depuis un autre module, il invoque la fonction originale au lieu de la version simulée :

>>> from mymodule import MyObj
>>> util.get_content=Mock(return_value="chose simulée")
>>> m=MyObj()
>>> m.func()
"chose"

Contenu de mymodule.py

from util import get_content

class MyObj:    
    def func():
        get_content()

Donc ma question serait - comment puis-je invoquer la version simulée d'une fonction depuis l'intérieur d'un module que j'appelle ?

Il semble que le from module import function soit à blâmer ici, en ce sens qu'il ne pointe pas vers la fonction simulée.

68voto

KanAfghan Points 424

Le cas général serait d'utiliser patch de mock. Considérez ce qui suit :

utils.py

def get_content():
    return 'stuff'

mymodule.py

from util import get_content

class MyClass(object):

    def func(self):
        return get_content()

test.py

import unittest

from mock import patch

from mymodule import MyClass

class Test(unittest.TestCase):

    @patch('mymodule.get_content')
    def test_func(self, get_content_mock):
        get_content_mock.return_value = 'mocked stuff'

        my_class = MyClass()
        self.assertEqual(my_class.func(), 'mocked stuff')
        self.assertEqual(get_content_mock.call_count, 1)
        get_content_mock.assert_called_once()

Remarquez comment get_content est mocké, ce n'est pas util.get_content, mais plutôt mymodule.get_content car nous l'utilisons dans mymodule.

Le code ci-dessus a été testé avec mock v2.0.0, nosetests v1.3.7 et python v2.7.9.

47voto

shreddd Points 3237

Je pense avoir trouvé une solution de contournement, bien que ce ne soit pas encore tout à fait clair sur la façon de résoudre le cas général

Dans mymodule, si je remplace

from util import get_content

class MyObj:    
    def func():
        get_content()

par

import util

class MyObj:    
    def func():
        util.get_content()

Le Mock semble être invoqué. Il semble que les espaces de noms doivent correspondre (ce qui a du sens). Cependant, la chose étrange est que je m'attends à

import mymodule
mymodule.get_content = mock.Mock(return_value="mocked stuff")

que cela fonctionne dans le cas original où j'utilise la syntaxe from/import (qui intègre désormais get_content dans mymodule). Mais cela fait toujours référence à get_content non muni.

Il s'avère que l'espace de noms est important - il suffit d'en tenir compte lors de l'écriture de votre code.

29voto

Simon Luijk Points 76

Vous devez patcher la fonction là où elle est utilisée. Dans votre cas, ce serait dans le module mymodule.

import mymodule
>>> mymodule.get_content = Mock(return_value="mocked stuff")
>>> m = mymodule.MyObj()
>>> m.func()
"mocked stuff"

Il y a une référence dans la documentation ici: http://docs.python.org/dev/library/unittest.mock.html#where-to-patch

12voto

samplebias Points 19805

Supposons que vous créez votre simulation à l'intérieur du module foobar:

import util, mock
util.get_content = mock.Mock(return_value="éléments simulés")

Si vous importez mymodule et appelez util.get_content sans avoir d'abord importé foobar, votre simulation ne sera pas installée:

import util
def func()
    print util.get_content()
func()
"éléments"

Au lieu de cela:

import util
import foobar   # substitue la simulation
def func():
    print util.get_content()
func()
"éléments simulés"

Notez que foobar peut être importé n'importe où (le module A importe B qui importe foobar) tant que foobar est évalué avant que util.get_content soit appelé.

2voto

JorgePM Points 79

Bien que cela ne fournisse pas directement une réponse à votre question, une autre alternative possible est de transformer votre fonction en une méthode statique en utilisant le @staticmethod.

Ainsi, vous pourriez transformer votre module utils en une classe en utilisant quelque chose comme:

class util(object):
     @staticmethod
     def get_content():
         return "stuff"

Ensuite, simulez correctement les correctifs.

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