Come posso deridere un open utilizzato in un'istruzione with (utilizzando il framework Mock in Python)?
-
18-09-2019 - |
Domanda
Come posso testare il seguente codice con i mock (usando i mock, il decoratore di patch e le sentinelle fornite da Il framework Mock di Michael Foord):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Soluzione
Il modo per farlo è cambiato in finto 0.7.0 che finalmente supporta beffardo i metodi di protocollo di pitone (metodi magici), in particolare utilizzando il MagicMock:
http://www.voidspace.org.uk/python/mock/magicmock.html
Un esempio di scherno aperta come un contesto manager (dalla pagina esempi nella documentazione finto):
>>> 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')
Altri suggerimenti
Non c'è molto rumore in queste risposte; quasi tutti sono corretti ma obsolete e non pulito. mock_open
fa parte di quadro mock
ed è molto semplice da usare. patch
utilizzato come contesto restituisce l'oggetto utilizzato per sostituire quello patchato :. si può utilizzare per rendere il test più semplice
Python 3.x
Usa builtins
invece di __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
non fa parte del unittest
e si dovrebbe patchare __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")
caso Decorator
Se si utilizza patch
come decoratrice usando il risultato di mock_open()
come argomento della new
patch
può essere un po 'strano.
In questo caso è meglio usare l'argomento del new_callable
patch
e ricordare che ogni argomenti extra che patch
non usa verranno passati al new_callable
funzione come descritto in documentazione patch
.
patch () prende argomenti chiave arbitrari. Questi saranno passati al Mock (o new_callable) sulla costruzione.
Per esempio versione decorata per Python 3.x è:
@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")
Ricordate che in questo caso patch
aggiungerà l'oggetto fittizio come argomento di voi funzione di test.
Con le ultime versioni di finto, è possibile utilizzare il mock_open helper:
mock_open (finto = Nessuno, read_data = Nessuno)
Una funzione di supporto per creare una finto per sostituire l'uso di open. Si lavora per aprire chiamato direttamente o utilizzato come un contesto manager.
L'argomento finto è l'oggetto fittizio da configurare. Se None (il default) poi un MagicMock verrà creato per voi, con l'API limitata a metodi o attributi disponibili sulle maniglie di file standard.
read_data è una stringa per il metodo di lettura del handle di file per ritorno. Questa è una stringa vuota di default.
>>> 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')
Usare mock_open per un file semplice read()
(lo snippet originale mock_open già riportato in questa pagina è più adatto alla scrittura):
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 come da documentazione per mock_open, questo è specifico per read()
, quindi non funzionerà con modelli comuni come for line in f
, Per esempio.
Utilizza Python 2.6.6 / mock 1.0.1
Potrei essere un po 'tardi per il gioco, ma questo ha funzionato per me quando si chiama open
in un altro modulo, senza dover creare un nuovo file.
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")
Con l'applicazione di patch la funzione open
all'interno del modulo __builtin__
al mio mock_open()
, posso prendere in giro la scrittura in un file senza creare uno.
Nota: Se si utilizza un modulo che utilizza Cython, o il vostro programma dipende Cython in qualsiasi modo, è necessario importare modulo __builtin__
di Cython nofollow includendo import __builtin__
nella parte superiore del file. Non sarà in grado di prendere in giro il __builtin__
universale se si utilizza Cython.
La risposta superiore è utile, ma ho ampliato un po '.
Se si desidera impostare il valore del vostro oggetto file (il f
in as f
) sulla base degli argomenti passati al open()
Ecco un modo per farlo:
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
Fondamentalmente, open()
restituirà un oggetto e with
chiamerà __enter__()
su quell'oggetto.
Per deridere correttamente, dobbiamo prendere in giro open()
per restituire un oggetto fittizio. Questo oggetto fittizio dovrebbe quindi prendere in giro la chiamata __enter__()
su di esso (MagicMock
farà questo per noi) per restituire l'oggetto di dati finte / file che vogliamo (da qui mm.__enter__.return_value
). Fare questo con 2 prende in giro il modo sopra di noi permette di catturare gli argomenti passati al open()
e passarle al nostro metodo do_something_with_data
.
ho passato un intero file finto come una stringa di open()
e la mia do_something_with_data
si presentava così:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
Questo trasforma la stringa in una lista in modo da poter eseguire le seguenti operazioni come si farebbe con un normale file:
for line in file:
#do action