Bagaimana cara menguji kode berikut dengan mengolok-olok (menggunakan mengolok-olok, dekorator patch dan penjaga disediakan oleh kerangka kerja Mock Michael Foord ):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Bagaimana cara menguji kode berikut dengan mengolok-olok (menggunakan mengolok-olok, dekorator patch dan penjaga disediakan oleh kerangka kerja Mock Michael Foord ):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Jawaban:
Cara untuk melakukan ini telah berubah di mock 0.7.0 yang akhirnya mendukung mengejek metode protokol python (metode ajaib), terutama menggunakan MagicMock:
http://www.voidspace.org.uk/python/mock/magicmock.html
Contoh mengejek terbuka sebagai manajer konteks (dari halaman contoh dalam dokumentasi tiruan):
>>> 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__
dan __exit__
untuk mengolok-olok objek juga - apakah pendekatan yang terakhir sudah ketinggalan zaman, atau masih berguna?
file
hilang!
mock_open
adalah bagian dari mock
framework dan sangat mudah digunakan. patch
digunakan sebagai konteks mengembalikan objek yang digunakan untuk menggantikan yang ditambal: Anda dapat menggunakannya untuk membuat tes Anda lebih sederhana.
Gunakan builtins
sebagai ganti __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
bukan bagian dari unittest
dan Anda harus menambal__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")
Jika Anda akan menggunakan patch
sebagai dekorator menggunakan mock_open()
hasil sebagai new
patch
argumen bisa sedikit aneh.
Dalam hal ini lebih baik menggunakan new_callable
patch
argumen dan ingat bahwa setiap argumen tambahan yang patch
tidak digunakan akan diteruskan ke new_callable
fungsi seperti yang dijelaskan dalam patch
dokumentasi .
patch () mengambil argumen kata kunci sewenang-wenang. Ini akan diteruskan ke Mock (atau new_callable) pada konstruksi.
Misalnya versi yang didekorasi untuk Python 3.x adalah:
@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")
Ingatlah bahwa dalam hal ini patch
akan menambahkan objek tiruan sebagai argumen fungsi pengujian Anda.
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
dikonversi menjadi sintaksis dekorator? Saya sudah mencoba, tetapi saya tidak yakin apa yang perlu saya sampaikan @patch("builtins.open", ...)
sebagai argumen kedua.
return_value
dari mock_open
ke objek tiruan lain dan menegaskan mock kedua ini return_value
), tapi itu bekerja dengan menambahkan mock_open
sebagai new_callable
.
six
modul untuk memiliki mock
modul yang konsisten . Tapi saya tidak tahu apakah itu memetakan juga builtins
dalam modul umum.
Dengan versi mock terbaru, Anda dapat menggunakan pembantu mock_open yang sangat berguna :
mock_open (mock = Tidak Ada, read_data = Tidak Ada)
Fungsi pembantu untuk membuat tiruan untuk menggantikan penggunaan terbuka. Ini berfungsi untuk terbuka yang dipanggil langsung atau digunakan sebagai manajer konteks.
Argumen mock adalah objek tiruan untuk dikonfigurasi. Jika Tidak Ada (default) maka MagicMock akan dibuat untuk Anda, dengan API terbatas pada metode atau atribut yang tersedia pada pegangan file standar.
read_data adalah string untuk metode baca dari pegangan file untuk kembali. Ini adalah string kosong secara 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')
.write
panggilan?
handle.write.assert_any_call()
. Anda juga dapat menggunakan handle.write.call_args_list
untuk mendapatkan setiap panggilan jika urutannya penting.
m.return_value.write.assert_called_once_with('some stuff')
lebih baik imo. Hindari mendaftarkan panggilan.
Mock.call_args_list
lebih aman daripada memanggil Mock.assert_xxx
metode apa pun. Jika Anda salah mengeja salah satu dari yang terakhir, menjadi atribut Mock, mereka akan selalu diam-diam berlalu.
Untuk menggunakan mock_open untuk file sederhana read()
(snipet mock_open asli yang sudah diberikan pada halaman ini lebih diarahkan untuk menulis):
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()
Catatan sesuai dokumen untuk mock_open, ini khusus untuk read()
, jadi tidak akan berfungsi dengan pola umum sepertifor line in f
, misalnya.
Menggunakan python 2.6.6 / mock 1.0.1
for line in opened_file:
jenis kode. Saya mencoba bereksperimen dengan StringIO yang dapat diterapkan yang mengimplementasikan __iter__
dan menggunakannya bukan my_text
, tetapi tidak berhasil.
read()
jadi tidak akan berfungsi dalam for line in opened_file
kasus Anda ; Saya telah mengedit posting untuk memperjelas
for line in f:
Dukungan @EvgeniiPuchkaryov dapat dicapai dengan mengejek nilai pengembalian open()
sebagai objek StringIO .
with open("any_string") as f: print f.read()
Jawaban teratas berguna tetapi saya sedikit memperluasnya.
Jika Anda ingin menetapkan nilai objek file Anda ( f
dalam as f
) berdasarkan argumen yang diteruskan ke open()
berikut adalah salah satu cara untuk melakukannya:
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
Pada dasarnya, open()
akan mengembalikan suatu objek dan with
akan memanggil __enter__()
objek itu.
Untuk mengejek dengan benar, kita harus mengejek open()
untuk mengembalikan objek tiruan. Objek tiruan itu kemudian harus mengejek __enter__()
panggilan di atasnya ( MagicMock
akan melakukan ini untuk kita) untuk mengembalikan objek data / file tiruan yang kita inginkan (karenanya mm.__enter__.return_value
). Melakukan ini dengan 2 mengejek cara di atas memungkinkan kita untuk menangkap argumen yang diteruskan ke open()
dan meneruskannya ke do_something_with_data
metode kita .
Saya melewati seluruh file tiruan sebagai string open()
dan saya do_something_with_data
tampak seperti ini:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
Ini mengubah string menjadi daftar sehingga Anda dapat melakukan hal berikut seperti yang Anda lakukan dengan file normal:
for line in file:
#do action
__enter__
? Ini jelas lebih mirip hack daripada cara yang disarankan.
Saya mungkin agak terlambat ke permainan, tetapi ini berhasil bagi saya ketika memanggil open
modul lain tanpa harus membuat file baru.
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")
Dengan menambal open
fungsi di dalam __builtin__
modul kemock_open()
, saya bisa mengejek menulis ke file tanpa membuat satu.
Catatan: Jika Anda menggunakan modul yang menggunakan cython, atau program Anda bergantung pada cython, Anda perlu mengimpor modul cython__builtin__
dengan memasukkan import __builtin__
di bagian atas file Anda. Anda tidak akan dapat mengejek universal __builtin__
jika Anda menggunakan cython.
import __builtin__
modul pengujian saya. Artikel ini membantu memperjelas mengapa teknik ini bekerja sebaik itu: ichimonji10.name/blog/6
Ini berfungsi untuk tambalan untuk membaca konfigurasi json.
class ObjectUnderTest:
def __init__(self, filename: str):
with open(filename, 'r') as f:
dict_content = json.load(f)
Objek yang dipermainkan adalah objek io.TextIOWrapper yang dikembalikan oleh fungsi 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):
Jika Anda tidak memerlukan file lebih jauh, Anda dapat menghias metode pengujian:
@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
result = testeme()
assert result == "data"