mock python unit-testing mocking iteration python-mock

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