Jak przetestować następujący kod za pomocą próbnych (używając próbnych, dekoratora łatek i wartowników dostarczonych przez frameworka Michaela Foorda ):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Jak przetestować następujący kod za pomocą próbnych (używając próbnych, dekoratora łatek i wartowników dostarczonych przez frameworka Michaela Foorda ):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Odpowiedzi:
Sposób na dokonanie tego zmienił się w pozornej wersji 0.7.0, która wreszcie obsługuje wyśmiewanie metod protokołu python (metody magiczne), szczególnie przy użyciu MagicMock:
http://www.voidspace.org.uk/python/mock/magicmock.html
Przykład wyśmiewania otwarty jako menedżer kontekstu (ze strony przykładów w dokumentacji próbnej):
>>> 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__
i __exit__
wyśmiewa również obiekty - czy to drugie podejście jest nieaktualne, czy nadal przydatne?
file
nie ma!
mock_open
jest częścią mock
frameworka i jest bardzo prosty w użyciu. patch
użyte jako kontekst zwraca obiekt użyty do zastąpienia załatanego: możesz go użyć, aby uprościć test.
Użyj builtins
zamiast __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
nie jest częścią unittest
i powinieneś łatać__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")
Jeśli użyjesz patch
jako dekoratora, używając mock_open()
wyniku jako new
patch
argumentu, może to być trochę dziwne.
W takim przypadku lepiej użyć new_callable
patch
argumentu i pamiętać, że każdy dodatkowy argument, patch
który nie będzie używany, zostanie przekazany do new_callable
działania zgodnie z opisem w patch
dokumentacji .
patch () przyjmuje dowolne argumenty słów kluczowych. Zostaną one przekazane Mock (lub new_callable) podczas budowy.
Na przykład udekorowana wersja dla Python 3.x to:
@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")
Pamiętaj, że w tym przypadku patch
doda obiekt próbny jako argument funkcji testowej.
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
można przekonwertować na składnię dekoratora? Próbowałem, ale nie jestem pewien, na co muszę przejść @patch("builtins.open", ...)
jako drugi argument.
return_value
of mock_open
na inny próbny obiekt i potwierdzić drugie próbne return_value
), ale zadziałało, dodając mock_open
as new_callable
.
six
moduł, aby mieć spójny mock
moduł. Ale nie wiem, czy mapuje również builtins
we wspólnym module.
Dzięki najnowszym wersjom mocka możesz użyć naprawdę przydatnego pomocnika mock_open :
mock_open (mock = None, read_data = None)
Funkcja pomocnika do stworzenia makiety, która zastąpi użycie open. Działa dla open wywoływany bezpośrednio lub używany jako menedżer kontekstu.
Argument próbny to próbny obiekt do skonfigurowania. Jeśli Brak (domyślnie), zostanie utworzony MagicMock, z API ograniczonym do metod lub atrybutów dostępnych w standardowych uchwytach plików.
read_data to ciąg znaków zwracany przez metodę read uchwytu pliku. Domyślnie jest to pusty ciąg.
>>> 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
połączeń?
handle.write.assert_any_call()
. Możesz także użyć, handle.write.call_args_list
aby odebrać każde połączenie, jeśli kolejność jest ważna.
m.return_value.write.assert_called_once_with('some stuff')
jest lepsze imo. Unika rejestracji połączenia.
Mock.call_args_list
jest bezpieczniejsze niż wywoływanie którejkolwiek z Mock.assert_xxx
metod. Jeśli źle przeliterujesz którykolwiek z tych drugich, będąc atrybutami Mocka, zawsze będą cicho przechodzić.
Aby użyć mock_open dla prostego pliku read()
(oryginalny fragment mock_open już podany na tej stronie jest przeznaczony bardziej do zapisu):
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()
Uwaga, jak w dokumentacji dla mock_open, jest to specjalnie dla read()
, więc nie będzie działać z typowymi wzorcami, takimi jak for line in f
na przykład.
Używa języka Python 2.6.6 / mock 1.0.1
for line in opened_file:
rodzajem kodu. Próbowałem eksperymentować z iterowalnym StringIO, które implementuje __iter__
i używa tego zamiast my_text
, ale bez powodzenia.
read()
więc nie będzie działać w twoim for line in opened_file
przypadku; Zredagowałem post, aby wyjaśnić
for line in f:
można osiągnąć przez wyśmiewanie wartości zwracanej open()
jako obiektu StringIO .
with open("any_string") as f: print f.read()
Najlepsza odpowiedź jest przydatna, ale trochę ją rozwinąłem.
Jeśli chcesz ustawić wartość swojego obiektu pliku ( f
in as f
) na podstawie argumentów przekazanych open()
tutaj, możesz to zrobić w następujący sposób:
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
Zasadniczo open()
zwróci obiekt i with
wywoła __enter__()
ten obiekt.
Aby poprawnie kpić, musimy kpić, open()
aby zwrócić próbny obiekt. Ten próbny obiekt powinien następnie wyśmiewać __enter__()
wywołanie ( MagicMock
zrobi to za nas), aby zwrócić próbny obiekt danych / pliku, który chcemy (stąd mm.__enter__.return_value
). Wykonanie tego przy użyciu 2 próbnych sposobów w powyższy sposób pozwala nam uchwycić przekazane argumenty open()
i przekazać je naszymdo_something_with_data
metodzie.
Przekazałem cały próbny plik jako ciąg znaków open()
i do_something_with_data
wyglądałem tak:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
To przekształca ciąg w listę, dzięki czemu możesz wykonać następujące czynności, tak jak w przypadku normalnego pliku:
for line in file:
#do action
__enter__
? To zdecydowanie bardziej przypomina hack niż zalecany sposób.
Mogę być trochę spóźniony do gry, ale zadziałało to dla mnie, gdy zadzwoniłem open
do innego modułu bez konieczności tworzenia nowego pliku.
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")
Przez łatanie open
funkcji w __builtin__
moim module mock_open()
mogę wyśmiewać zapisywanie do pliku bez jego tworzenia.
Uwaga: Jeśli korzystasz z modułu, który używa cytonu, lub twój program w jakikolwiek sposób zależy od cytonu, musisz zaimportować moduł cytonu, umieszczając__builtin__
go import __builtin__
na górze pliku. Jeśli __builtin__
używasz cytonu, nie będziesz mógł kpić z tego uniwersalnego .
import __builtin__
do mojego modułu testowego. Ten artykuł pomógł wyjaśnić, dlaczego ta technika działa tak dobrze, jak ona: ichimonji10.name/blog/6
Działa to w przypadku poprawki do odczytu konfiguracji json.
class ObjectUnderTest:
def __init__(self, filename: str):
with open(filename, 'r') as f:
dict_content = json.load(f)
Wyśmiewanym obiektem jest obiekt io.TextIOWrapper zwrócony przez funkcję 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):
Jeśli nie potrzebujesz już żadnego pliku, możesz udekorować metodę testową:
@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
result = testeme()
assert result == "data"