¿Cómo me burlo de un open usado en una declaración with (usando el marco Mock en Python)?
-
18-09-2019 - |
Pregunta
¿Cómo pruebo el siguiente código con simulacros (usando simulacros, el decorador de parches y los centinelas proporcionados por Marco simulado de Michael Foord):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Solución
La manera de hacer esto ha cambiado en mock 0.7.0 que finalmente soporta burlarse de los métodos de protocolo pitón (métodos mágicos), en particular mediante el MagicMock:
http://www.voidspace.org.uk/python/mock/magicmock.html
Un ejemplo de burlarse abierto como un gestor de contexto (desde la página de ejemplos en la documentación mock):
>>> 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')
Otros consejos
Hay mucho ruido en estas respuestas;casi todos son correctos pero están desactualizados y no están ordenados. mock_open
es parte de mock
framework y es muy sencillo de utilizar. patch
usado como contexto devuelve el objeto usado para reemplazar el parcheado:puedes usarlo para simplificar tu prueba.
Pitón 3.x
Usar builtins
en lugar 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")
Pitón 2.7
mock
no es parte de unittest
y deberías parchear __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")
Estuche decorador
si usaras patch
como decorador usando mock_open()
El resultado como new
patch
El argumento puede ser un poco extraño.
En este caso es mejor utilizar el new_callable
patch
argumento y recuerda que cada argumento adicional que patch
no utiliza se pasará a new_callable
funcionar como se describe en patch
documentación.
patch() toma argumentos de palabras clave arbitrarios.Estos se pasarán al Mock (o new_callable) en la construcción.
Por ejemplo, versión decorada para Pitón 3.x es:
@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")
Recuerda que en este caso patch
Agregará el objeto simulado como argumento de su función de prueba.
Con las últimas versiones del simulacro, se puede utilizar el mock_open helper:
mock_open (mock = Ninguno, read_data = None)
Una función auxiliar para crear una maqueta para reemplazar el uso de abierta. Funciona para abierto llamada directa o utilizado como un gestor de contexto.
El argumento es el simulacro objeto de burla para configurar. Si Ninguno (el predeterminado), entonces se creará un MagicMock para usted, con la API limitado a los métodos o atributos disponibles en los mangos de archivo estándar.
read_data es una cadena para el método de lectura del identificador de archivo regreso. Esta es una cadena vacía por defecto.
>>> 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')
Para usar mock_open de un simple archivo read()
(el original mock_open fragmento ya dada en esta página se orienta más para escribir):
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()
Nota según documentación para mock_open, esto es específicamente para read()
, por lo que no funcionará con patrones comunes como for line in f
, por ejemplo.
Utiliza Python 2.6.6 / 1.0.1 simulacro
Puede ser que sea un poco tarde al juego, pero esta trabajado para mí al llamar open
en otro módulo sin tener que crear un nuevo archivo.
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")
Por parchear la función open
el interior del módulo __builtin__
a mi mock_open()
, que puede burlarse de la escritura en un archivo sin crear uno.
Nota: Si está utilizando un módulo que utiliza Cython, o su programa depende de Cython de cualquier manera, tendrá que importar módulo de __builtin__
Cython nofollow incluyendo import __builtin__
en la parte superior de su archivo. Usted no será capaz de burlarse de la __builtin__
universal, si está utilizando Cython.
La respuesta más común es útil pero se expandió en él un poco.
Si desea establecer el valor de su objeto de archivo (el f
en as f
) sobre la base de los argumentos que se pasan a open()
aquí es una manera de hacerlo:
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
Básicamente, open()
devolverá un objeto y with
llamará __enter__()
en ese objeto.
Para burlarse correctamente, hay que burlarse open()
para devolver un objeto de burla. Ese objeto de burla luego se burle la llamada __enter__()
en él (MagicMock
lo hará por nosotros) para devolver el objeto de datos simulados / archivo que queremos (de ahí mm.__enter__.return_value
). Hacer esto con 2 burla de la forma anterior nos permite capturar los argumentos que se pasan a open()
y pasarlos a nuestro método do_something_with_data
.
pasé todo un archivo de maqueta como una cadena de open()
y mi do_something_with_data
veía así:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
Esto transforma la cadena en una lista para que pueda hacer lo siguiente como lo haría con un archivo normal:
for line in file:
#do action