Comment tester le code suivant avec des simulacres (à l'aide de simulacres, du décorateur de patch et des sentinelles fournis par framework Mock de Michael Foord ):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
La manière de faire cela a changé dans la maquette 0.7.0 qui supporte enfin de se moquer des méthodes de protocole python (méthodes magiques), en particulier en utilisant MagicMock:
http://www.voidspace.org.uk/python/mock/magicmock.html
Un exemple de moquage ouvert en tant que gestionnaire de contexte (à partir de la page des exemples dans la documentation fictive):
>>> open_name = '%s.open' % __name__
>>> with patch(open_name, create=True) as mock_open:
... mock_open.return_value = MagicMock(spec=file)
...
... with open('/some/path', 'w') as f:
... f.write('something')
...
<mock.Mock object at 0x...>
>>> file_handle = mock_open.return_value.__enter__.return_value
>>> file_handle.write.assert_called_with('something')
Il y a beaucoup de bruit dans ces réponses; presque tous sont corrects mais obsolètes et pas soigné. mock_open
fait partie du cadre mock
et est très simple à utiliser. patch
utilisé en tant que contexte renvoie l'objet utilisé pour remplacer l'objet corrigé: vous pouvez l'utiliser pour simplifier votre test.
Utilisez builtins
au lieu de __builtin__
.
from unittest.mock import patch, mock_open
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")
mock
ne fait pas partie de unittest
et vous devriez patcher __builtin__
from mock import patch, mock_open
with patch("__builtin__.open", mock_open(read_data="data")) as mock_file:
assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")
Si vous utilisiez patch
comme décorateur, utilisez le résultat de mock_open()
en tant que l'argument de new
patch
peut être un peu bizarre.
Dans ce cas, il est préférable d’utiliser l’argument de new_callable
patch
et de garder à l’esprit que tous les arguments supplémentaires que patch
n’utilise pas seront passés à la fonction new_callable
En tant que décrit dans patch
documentation .
patch () prend des mots-clés arbitraires. Ceux-ci seront passés à la maquette (ou new_callable) lors de la construction.
Par exemple, la version décorée pour Python 3.x est:
@patch("builtins.open", new_callable=mock_open, read_data="data")
def test_patch(mock_file):
assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")
Rappelez-vous que dans ce cas, patch
ajoutera l'objet fictif en tant qu'argument de votre fonction test.
Avec les dernières versions de mock, vous pouvez utiliser l’assistant vraiment utile mock_open :
mock_open (mock = None, read_data = None)
Une fonction d'assistance pour créer une maquette pour remplacer l'utilisation de open. Cela fonctionne pour open appelé directement ou utilisé en tant que gestionnaire de contexte.
L'argument fictif est l'objet fictif à configurer. Si aucun (valeur par défaut), un MagicMock sera créé pour vous, avec l'API limitée aux méthodes ou attributs disponibles sur les descripteurs de fichiers standard.
read_data est une chaîne que la méthode de lecture du descripteur de fichier doit renvoyer. Ceci est une chaîne vide par défaut.
>>> from mock import mock_open, patch
>>> m = mock_open()
>>> with patch('{}.open'.format(__name__), m, create=True):
... with open('foo', 'w') as h:
... h.write('some stuff')
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')
Pour utiliser mock_open pour un fichier simple read()
(l'extrait de code mock_open d'origine déjà donné sur cette page est plus adapté pour l'écriture):
my_text = "some text to return when read() is called on the file object"
mocked_open_function = mock.mock_open(read_data=my_text)
with mock.patch("__builtin__.open", mocked_open_function):
with open("any_string") as f:
print f.read()
Notez que selon docs pour mock_open, c'est spécifiquement pour read()
, donc ne fonctionnera pas avec des modèles courants comme for line in f
, Par exemple.
Utilise python 2.6.6/mock 1.0.1
Je suis peut-être un peu en retard dans le jeu, mais cela a fonctionné pour moi lorsque j’appelle open
dans un autre module sans avoir à créer un nouveau fichier.
test.py
import unittest
from mock import Mock, patch, mock_open
from MyObj import MyObj
class TestObj(unittest.TestCase):
open_ = mock_open()
with patch.object(__builtin__, "open", open_):
ref = MyObj()
ref.save("myfile.txt")
assert open_.call_args_list == [call("myfile.txt", "wb")]
MyObj.py
class MyObj(object):
def save(self, filename):
with open(filename, "wb") as f:
f.write("sample text")
En corrigeant la fonction open
du module __builtin__
Sur ma mock_open()
, je peux me moquer d'écrire dans un fichier sans en créer un.
Remarque: Si vous utilisez un module utilisant cython ou si votre programme en dépend, vous devrez importer le module __builtin__
De cython en incluant import __builtin__
en haut de votre dossier. Vous ne pourrez pas vous moquer de l'universel __builtin__
Si vous utilisez Cython.
La réponse principale est utile, mais je l'ai un peu développée.
Si vous souhaitez définir la valeur de votre objet de fichier (le f
dans as f
) En fonction des arguments passés à open()
, voici une façon de le faire:
def save_arg_return_data(*args, **kwargs):
mm = MagicMock(spec=file)
mm.__enter__.return_value = do_something_with_data(*args, **kwargs)
return mm
m = MagicMock()
m.side_effect = save_arg_return_array_of_data
# if your open() call is in the file mymodule.animals
# use mymodule.animals as name_of_called_file
open_name = '%s.open' % name_of_called_file
with patch(open_name, m, create=True):
#do testing here
Fondamentalement, open()
retournera un objet et with
appellera __enter__()
sur cet objet.
Pour se moquer correctement, nous devons nous moquer de open()
pour renvoyer un objet faux. Cet objet fictif devrait alors simuler l'appel __enter__()
(MagicMock
le fera pour nous) pour renvoyer l'objet fictif/fichier que nous voulons (d'où mm.__enter__.return_value
). Faire cela avec 2 simulacre la manière ci-dessus nous permet de capturer les arguments passés à open()
et de les transmettre à notre méthode do_something_with_data
.
J'ai passé tout un fichier fictif sous forme de chaîne à open()
et mon do_something_with_data
Ressemblait à ceci:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
Cela transforme la chaîne en une liste afin que vous puissiez effectuer les opérations suivantes comme vous le feriez avec un fichier normal:
for line in file:
#do action