python - Personalizar unittest.mock.mock_open para iteración
mock install (3)
A partir de Python 3.6, el objeto simulado de archivo devuelto por el método unittest.mock_open
no admite iteración . Este error se informó en 2014 y aún está abierto a partir de 2017.
Por lo tanto, un código como este produce silenciosamente cero iteraciones:
f_open = unittest.mock.mock_open(read_data=''foo/nbar/n'')
f = f_open(''blah'')
for line in f:
print(line)
Puede solucionar esta limitación agregando un método al objeto simulado que devuelve un iterador de línea adecuado:
def mock_open(*args, **kargs):
f_open = unittest.mock.mock_open(*args, **kargs)
f_open.return_value.__iter__ = lambda self : iter(self.readline, '''')
return f_open
¿Cómo debo personalizar unittest.mock.mock_open para manejar este código?
file: impexpdemo.py
def import_register(register_fn):
with open(register_fn) as f:
return [line for line in f]
Mi primer intento lo intenté con read_data
.
class TestByteOrderMark1(unittest.TestCase):
REGISTER_FN = ''test_dummy_path''
TEST_TEXT = [''test text 1/n'', ''test text 2/n'']
def test_byte_order_mark_absent(self):
m = unittest.mock.mock_open(read_data=self.TEST_TEXT)
with unittest.mock.patch(''builtins.open'', m):
result = impexpdemo.import_register(self.REGISTER_FN)
self.assertEqual(result, self.TEST_TEXT)
Esto falló, presumiblemente porque el código no usa read, readline o readlines. La documentation para unittest.mock.mock_open dice, "read_data es una cadena para los métodos read (), readline () y readlines () del identificador de archivo que se devolverá. Las llamadas a esos métodos tomarán datos de read_data hasta que se agoten. . La simulación de estos métodos es bastante simplista. Si necesita más control sobre los datos que está suministrando al código probado, necesitará personalizar esta simulación para usted. Read_data es una cadena vacía por defecto ".
Como la documentación no daba ninguna pista sobre qué tipo de personalización se requeriría, probé return_value
y side_effect
. Tampoco funcionó.
class TestByteOrderMark2(unittest.TestCase):
REGISTER_FN = ''test_dummy_path''
TEST_TEXT = [''test text 1/n'', ''test text 2/n'']
def test_byte_order_mark_absent(self):
m = unittest.mock.mock_open()
m().side_effect = self.TEST_TEXT
with unittest.mock.patch(''builtins.open'', m):
result = impexpdemo.import_register(self.REGISTER_FN)
self.assertEqual(result, self.TEST_TEXT)
El objeto mock_open()
no implementa la iteración.
Si no está utilizando el objeto de archivo como administrador de contexto, podría usar:
m = unittest.mock.MagicMock(name=''open'', spec=open)
m.return_value = iter(self.TEST_TEXT)
with unittest.mock.patch(''builtins.open'', m):
Ahora open()
devuelve un iterador, algo que se puede iterar directamente sobre un objeto de archivo, y también funcionará con next()
. Sin embargo, no se puede utilizar como administrador de contexto.
Puede combinar esto con mock_open()
luego proporcionar un método __iter__
y __next__
en el valor de retorno, con el beneficio agregado de que mock_open()
también agrega los requisitos previos para el uso como administrador de contexto:
# Note: read_data must be a string!
m = unittest.mock.mock_open(read_data=''''.join(self.TEST_TEXT))
m.return_value.__iter__ = lambda self: self
m.return_value.__next__ = lambda self: next(iter(self.readline, ''''))
El valor de retorno aquí es un objeto MagicMock especificado desde el objeto de file
(Python 2) o los objetos de archivo en memoria (Python 3), pero solo se han __enter__
métodos de read
, write
y __enter__
.
Lo anterior no funciona en Python 2 porque a) Python 2 espera que exista el next
, no __next__
yb) next
no se trata como un método especial en simulacro (así es), así que incluso si cambia el nombre de __next__
a next
en lo anterior ejemplo, el tipo de valor de retorno no tendrá un método next
. Para la mayoría de los casos, sería suficiente hacer que el objeto de archivo produjera un iterable en lugar de un iterador con:
# Python 2!
m = mock.mock_open(read_data=''''.join(self.TEST_TEXT))
m.return_value.__iter__ = lambda self: iter(self.readline, '''')
Cualquier código que use iter(fileobj)
funcionará (incluido un bucle for
).
Hay un problema abierto en el rastreador de Python que apunta a remediar esta brecha.
He encontrado la siguiente solución:
text_file_data = ''/n''.join(["a line here", "the second line", "another
line in the file"])
with patch(''__builtin__.open'', mock_open(read_data=text_file_data),
create=True) as m:
# mock_open doesn''t properly handle iterating over the open file with for line in file:
# but if we set the return value like this, it works.
m.return_value.__iter__.return_value = text_file_data.splitlines()
with open(''filename'', ''rU'') as f:
for line in f:
print line