Come testare il codice seguente con i mock (usando i mock, il decoratore di patch e le sentinelle fornite dal framework Mock di Michael Foord ):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Come testare il codice seguente con i mock (usando i mock, il decoratore di patch e le sentinelle fornite dal framework Mock di Michael Foord ):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Risposte:
Il modo per farlo è cambiato in mock 0.7.0 che finalmente supporta il deridere i metodi del protocollo python (metodi magici), in particolare usando MagicMock:
http://www.voidspace.org.uk/python/mock/magicmock.html
Un esempio di derisione aperto come gestore di contesto (dalla pagina degli esempi nella documentazione di simulazione):
>>> 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')
__enter__
e __exit__
beffa anche gli oggetti: quest'ultimo approccio è obsoleto o è ancora utile?
file
è sparito!
mock_open
fa parte del mock
framework ed è molto semplice da usare. patch
usato come contesto restituisce l'oggetto usato per sostituire quello patchato: puoi usarlo per semplificare il tuo test.
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")
mock
non fa parte di unittest
e dovresti patch__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")
Se patch
usassi come decoratore usando mock_open()
il risultato come new
patch
argomento, può essere un po 'strano.
In questo caso è meglio usare l' new_callable
patch
argomento di e ricordare che tutti gli argomenti extra che patch
non usano saranno passati per new_callable
funzionare come descritto nella patch
documentazione .
patch () accetta argomenti di parole chiave arbitrari. Questi saranno passati al Mock (o new_callable) durante la costruzione.
Ad esempio la 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")
Ricorda che in questo caso patch
aggiungerai l'oggetto finto come argomento della tua funzione di test.
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
essere convertito in sintassi del decoratore? Ci ho provato, ma non sono sicuro di cosa devo passare @patch("builtins.open", ...)
come secondo argomento.
return_value
of mock_open
in un altro oggetto finto e affermare il secondo mock return_value
), ma ha funzionato aggiungendo mock_open
as new_callable
.
six
modulo per avere un mock
modulo coerente . Ma non so se mappare anche builtins
in un modulo comune.
Con le ultime versioni di mock, puoi usare l'utilissimo mock_open helper:
mock_open (mock = None, read_data = None)
Una funzione di aiuto per creare un finto per sostituire l'uso di open. Funziona per open chiamato direttamente o utilizzato come gestore di contesto.
L'argomento finto è l'oggetto finto da configurare. Se Nessuno (impostazione predefinita), verrà creato per te un MagicMock, con l'API limitata ai metodi o agli attributi disponibili sugli handle di file standard.
read_data è una stringa per la restituzione del metodo read dell'handle del file. Questa è una stringa vuota per impostazione predefinita.
>>> 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')
.write
chiamate?
handle.write.assert_any_call()
. Puoi anche usare handle.write.call_args_list
per ricevere ogni chiamata se l'ordine è importante.
m.return_value.write.assert_called_once_with('some stuff')
è meglio imo. Evita di registrare una chiamata.
Mock.call_args_list
è più sicuro che chiamare uno dei Mock.assert_xxx
metodi. Se si scrive erroneamente uno di questi ultimi, essendo attributi di Mock, passeranno sempre silenziosamente.
Per utilizzare mock_open per un file semplice read()
(lo snippet mock_open originale già indicato in questa pagina è più orientato 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 documenti per mock_open, questo è specifico per read()
, quindi non funzionerà con schemi comuni come for line in f
, ad esempio.
Utilizza python 2.6.6 / mock 1.0.1
for line in opened_file:
tipo di codice. Ho provato a sperimentare con StringIO iterabile che implementa __iter__
e usando quello invece di my_text
, ma senza fortuna.
read()
quindi non funzionerà nel tuo for line in opened_file
caso; Ho modificato il post per chiarire
for line in f:
supporto di @EvgeniiPuchkaryov può essere ottenuto deridendo invece il valore restituito open()
come oggetto StringIO .
with open("any_string") as f: print f.read()
La risposta migliore è utile ma l'ho ampliata un po '.
Se si desidera impostare il valore dell'oggetto file ( f
in as f
) in base agli argomenti passati a open()
questo è 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__()
quell'oggetto.
Per deridere correttamente, dobbiamo deridere open()
per restituire un oggetto finto. Quell'oggetto finto dovrebbe quindi deridere la __enter__()
chiamata su di esso ( MagicMock
lo farà per noi) per restituire l'oggetto dati / file fittizio che vogliamo (quindi mm.__enter__.return_value
). In questo modo con 2 beffe il modo sopra ci permette di catturare gli argomenti passati open()
e passarli al nostro do_something_with_data
metodo.
Ho passato un intero file simulato come una stringa open()
e il mio do_something_with_data
sembrava così:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
Questo trasforma la stringa in un elenco in modo da poter eseguire le seguenti operazioni come con un normale file:
for line in file:
#do action
__enter__
? Sembra decisamente più un trucco che un modo consigliato.
Potrei essere un po 'in ritardo al gioco, ma questo ha funzionato per me quando ho chiamato open
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")
Patchando la open
funzione all'interno del __builtin__
modulo sul mio mock_open()
, posso deridere la scrittura su un file senza crearne uno.
Nota: se si utilizza un modulo che utilizza cython o il programma dipende in qualche modo da cython, sarà necessario importare il __builtin__
modulo cython includendolo import __builtin__
nella parte superiore del file. Non sarai in grado di deridere l'universale __builtin__
se stai usando cython.
import __builtin__
al mio modulo di test. Questo articolo ha aiutato a chiarire perché questa tecnica funziona così come funziona: ichimonji10.name/blog/6
Questo ha funzionato per una patch per leggere una configurazione JSON.
class ObjectUnderTest:
def __init__(self, filename: str):
with open(filename, 'r') as f:
dict_content = json.load(f)
L'oggetto deriso è l'oggetto io.TextIOWrapper restituito dalla funzione open ()
@patch("<src.where.object.is.used>.open",
return_value=io.TextIOWrapper(io.BufferedReader(io.BytesIO(b'{"test_key": "test_value"}'))))
def test_object_function_under_test(self, mocker):
Se non hai bisogno di altri file, puoi decorare il metodo di test:
@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
result = testeme()
assert result == "data"