web-dev-qa-db-fra.com

Comment puis-je simuler une ouverture utilisée dans une instruction with (en utilisant le framework Mock en Python)?

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()
152
Daryl Spitzer

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')
123
fuzzyman

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.

Python 3.x

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")

Python 2.7

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")

Cas de décorateur

Si vous utilisiez patch comme décorateur, utilisez le résultat de mock_open() en tant que l'argument de newpatch peut être un peu bizarre.

Dans ce cas, il est préférable d’utiliser l’argument de new_callablepatch 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.

178
Michele d'Amico

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')
66
David

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

12
jlb83

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.

3
Leo C Han

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
3
theannouncer