Làm cách nào để kiểm tra mã sau bằng giả (sử dụng giả, trang trí bản vá và các câu lệnh được cung cấp bởi khung Mock của Michael Foord ):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Làm cách nào để kiểm tra mã sau bằng giả (sử dụng giả, trang trí bản vá và các câu lệnh được cung cấp bởi khung Mock của Michael Foord ):
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
Câu trả lời:
Cách thức thực hiện điều này đã thay đổi trong mock 0.7.0, cuối cùng hỗ trợ chế tạo các phương thức giao thức python (phương thức ma thuật), đặc biệt là sử dụng MagicMock:
http://www.voidspace.org.uk/python/mock/magicmock.html
Một ví dụ về giả định mở như một trình quản lý bối cảnh (từ trang ví dụ trong tài liệu giả):
>>> 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__
và __exit__
giả định các đối tượng - là cách tiếp cận sau đã lỗi thời hay vẫn còn hữu ích?
file
đã biến mất!
mock_open
là một phần của mock
khung và rất đơn giản để sử dụng. patch
được sử dụng làm bối cảnh trả về đối tượng được sử dụng để thay thế đối tượng đã vá: bạn có thể sử dụng nó để làm cho thử nghiệm của mình đơn giản hơn.
Sử dụng builtins
thay vì __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
không phải là một phần của unittest
và bạn nên vá__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")
Nếu bạn sử dụng patch
làm trang trí bằng cách sử dụng mock_open()
kết quả new
patch
của đối số thì có thể hơi lạ một chút.
Trong trường hợp này tốt hơn là sử dụng new_callable
patch
đối số và nhớ rằng mọi đối số bổ sung patch
không sử dụng sẽ được chuyển sang new_callable
chức năng như được mô tả trong patch
tài liệu .
patch () lấy các đối số từ khóa tùy ý. Chúng sẽ được chuyển đến Mock (hoặc new_callable) khi xây dựng.
Ví dụ phiên bản trang trí cho Python 3.x là:
@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")
Hãy nhớ rằng trong trường hợp patch
này sẽ thêm đối tượng giả làm đối số của hàm kiểm tra của bạn.
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
thể chuyển đổi thành cú pháp trang trí không? Tôi đã thử, nhưng tôi không chắc những gì tôi cần chuyển vào @patch("builtins.open", ...)
làm đối số thứ hai.
return_value
của mock_open
vào một đối tượng giả và khẳng định các mô hình thứ hai return_value
), nhưng nó làm việc bằng cách thêm mock_open
như new_callable
.
six
mô-đun để có một mock
mô-đun nhất quán . Nhưng tôi không biết nếu nó cũng ánh xạ builtins
trong một mô-đun chung.
Với các phiên bản mới nhất của giả, bạn có thể sử dụng trình trợ giúp mock_open thực sự hữu ích :
mock_open (mock = Không, read_data = Không)
Một chức năng trợ giúp để tạo ra một giả để thay thế việc sử dụng mở. Nó hoạt động để mở được gọi trực tiếp hoặc được sử dụng như một trình quản lý bối cảnh.
Đối số giả là đối tượng giả để cấu hình. Nếu Không có (mặc định) thì MagicMock sẽ được tạo cho bạn, với API giới hạn ở các phương thức hoặc thuộc tính có sẵn trên tay cầm tệp tiêu chuẩn.
read_data là một chuỗi cho phương thức đọc của tệp xử lý trả về. Đây là một chuỗi rỗng theo mặc định.
>>> 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
cuộc gọi?
handle.write.assert_any_call()
. Bạn cũng có thể sử dụng handle.write.call_args_list
để nhận từng cuộc gọi nếu đơn hàng quan trọng.
m.return_value.write.assert_called_once_with('some stuff')
là tốt hơn imo. Tránh đăng ký một cuộc gọi.
Mock.call_args_list
là an toàn hơn so với việc gọi bất kỳ Mock.assert_xxx
phương thức nào. Nếu bạn đánh vần sai bất kỳ từ nào sau này, là thuộc tính của Mock, chúng sẽ luôn âm thầm vượt qua.
Để sử dụng mock_open cho một tệp đơn giản read()
(đoạn mã mock_open ban đầu đã được cung cấp trên trang này được định hướng nhiều hơn để ghi):
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()
Lưu ý theo tài liệu cho mock_open, điều này đặc biệt dành cho read()
, vì vậy sẽ không hoạt động với các mẫu phổ biến như for line in f
, chẳng hạn.
Sử dụng trăn 2.6.6 / mock 1.0.1
for line in opened_file:
loại mã. Tôi đã thử trải nghiệm với StringIO có thể lặp lại thực hiện __iter__
và sử dụng nó thay vì my_text
, nhưng không có may mắn.
read()
vì vậy sẽ không hoạt động trong for line in opened_file
trường hợp của bạn ; Tôi đã chỉnh sửa bài đăng để làm rõ
for line in f:
hỗ trợ có thể đạt được bằng cách chế giễu giá trị trả về open()
là một đối tượng StringIO thay .
with open("any_string") as f: print f.read()
Câu trả lời hàng đầu là hữu ích nhưng tôi đã mở rộng về nó một chút.
Nếu bạn muốn đặt giá trị của đối tượng tệp của mình (phần f
trong as f
) dựa trên các đối số được truyền cho open()
đây, một cách để thực hiện:
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
Về cơ bản, open()
sẽ trả về một đối tượng và with
sẽ gọi __enter__()
đối tượng đó.
Để chế nhạo đúng cách, chúng ta phải giả lập open()
để trả về một đối tượng giả. Đối tượng giả đó sau đó sẽ mô phỏng __enter__()
cuộc gọi trên nó ( MagicMock
sẽ thực hiện điều này cho chúng tôi) để trả về đối tượng dữ liệu / tệp giả mà chúng tôi muốn (do đó mm.__enter__.return_value
). Làm điều này với 2 giả theo cách trên cho phép chúng tôi nắm bắt các đối số được truyền đến open()
và chuyển chúng sang do_something_with_data
phương thức của chúng tôi .
Tôi đã chuyển toàn bộ tệp giả thành một chuỗi open()
và tôi do_something_with_data
trông như thế này:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
Điều này biến đổi chuỗi thành một danh sách để bạn có thể thực hiện như sau với một tệp bình thường:
for line in file:
#do action
__enter__
? Nó chắc chắn trông giống như một hack hơn là một cách được đề xuất.
Tôi có thể hơi trễ trò chơi, nhưng điều này hiệu quả với tôi khi gọi open
trong một mô-đun khác mà không phải tạo một tệp mới.
kiểm tra
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")
Bằng cách vá open
chức năng bên trong __builtin__
mô-đun thành của tôi mock_open()
, tôi có thể giả định ghi vào một tệp mà không cần tạo.
Lưu ý: Nếu bạn đang sử dụng một mô-đun sử dụng cython hoặc chương trình của bạn phụ thuộc vào cython theo bất kỳ cách nào, bạn sẽ cần nhập mô-đun của cython__builtin__
bằng cách bao gồm import __builtin__
ở đầu tệp của bạn. Bạn sẽ không thể chế giễu phổ quát __builtin__
nếu bạn đang sử dụng cython.
import __builtin__
vào mô-đun thử nghiệm của tôi. Bài viết này đã giúp làm rõ lý do tại sao kỹ thuật này hoạt động tốt như nó: ichimonji10.name/blog/6
Điều này làm việc cho một bản vá để đọc một cấu hình json.
class ObjectUnderTest:
def __init__(self, filename: str):
with open(filename, 'r') as f:
dict_content = json.load(f)
Đối tượng bị giả là đối tượng io.TextIOWrapper được trả về bởi hàm 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):
Nếu bạn không cần thêm bất kỳ tập tin nào, bạn có thể trang trí phương pháp kiểm tra:
@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
result = testeme()
assert result == "data"