web-dev-qa-db-fra.com

fonctions de simulation utilisant python mock

J'essaie de Mock une fonction (qui retourne du contenu externe) en utilisant le module python mock (http://www.voidspace.org.uk/python/mock/index.html).

J'ai des problèmes pour se moquer des fonctions qui sont importées dans un module.

Par exemple, dans util.py j'ai

def get_content():
  return "stuff"

Je veux me moquer d'util.get_content pour qu'il renvoie autre chose.

J'essaye ceci:

util.get_content=Mock(return_value="mocked stuff")

Si get_content est invoqué dans un autre module, il ne semble jamais réellement renvoyer l'objet simulé. Suis-je en train de manquer quelque chose sur la façon d'utiliser Mock?

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

>>> util.get_content=Mock(return_value="mocked stuff")
>>> util.get_content()
"mocked stuff"

Cependant, si get_content est appelé à l'intérieur d'un autre module, il appelle la fonction d'origine au lieu de la version simulée:

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

Contenu de mymodule.py

from util import get_content

class MyObj:    
    def func():
        get_content()

Donc, je suppose que ma question est - comment puis-je invoquer la version Mocked d'une fonction à l'intérieur d'un module que j'appelle?

Il semble que le from module import function peut être à blâmer ici, car il ne pointe pas vers la fonction Mocked.

58
shreddd

Je pense avoir une solution de contournement, bien que je ne sois pas encore très clair sur la façon de résoudre le cas général

Dans mon module, si je remplace

from util import get_content

class MyObj:    
    def func():
        get_content()

avec

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 est logique). Cependant, la chose étrange est que je m'attendrais

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

pour faire l'affaire dans le cas d'origine où j'utilise la syntaxe from/import (qui tire maintenant get_content dans mymodule). Mais cela fait toujours référence au get_content non moqué.

Il s'avère que l'espace de noms est important - il suffit de garder cela à l'esprit lors de l'écriture de votre code.

28
shreddd

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 les documents ici: http://docs.python.org/dev/library/unittest.mock.html#where-to-patch

23
Simon Luijk

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

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

Si vous importez mymodule et appelez util.get_content sans importer au préalable foobar, votre maquette ne sera pas installée:

import util
def func()
    print util.get_content()
func()
"stuff"

Au lieu:

import util
import foobar   # substitutes the mock
def func():
    print util.get_content()
func()
"mocked stuff"

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

9
samplebias

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

tils.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()

Notez comment get_content est moqué, ce n'est pas util.get_content, plutôt mymodule.get_content puisque nous l'utilisons dans mymodule.

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

9
KanAfghan

Bien qu'il ne fournisse pas de réponse directe à votre question, une autre alternative possible consiste à transformer votre fonction en une méthode statique à l'aide de @staticmethod.

Vous pouvez donc transformer vos modules utils en une classe en utilisant quelque chose comme:

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

Ensuite, moquez-le correctement.

1
zom-pro