ฉันจะทดสอบโค้ดต่อไปนี้ด้วย mocks ได้อย่างไร (โดยใช้ mocks เครื่องมือตกแต่งแพทช์และรักษาการณ์ที่จัดทำโดยกรอบงานจำลองของ Michael Foord )
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
ฉันจะทดสอบโค้ดต่อไปนี้ด้วย mocks ได้อย่างไร (โดยใช้ mocks เครื่องมือตกแต่งแพทช์และรักษาการณ์ที่จัดทำโดยกรอบงานจำลองของ Michael Foord )
def testme(filepath):
with open(filepath, 'r') as f:
return f.read()
คำตอบ:
วิธีการทำเช่นนี้มีการเปลี่ยนแปลงใน mock 0.7.0 ซึ่งในที่สุดก็รองรับการเยาะเย้ยวิธีการโพรโทคอลหลาม
http://www.voidspace.org.uk/python/mock/magicmock.html
ตัวอย่างของการเยาะเย้ยเปิดเป็นตัวจัดการบริบท (จากหน้าตัวอย่างในเอกสารจำลอง):
>>> 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__และ__exit__จำลองวัตถุเช่นกัน - แนวทางหลังล้าสมัยหรือยังมีประโยชน์หรือไม่
fileหายไปแล้ว!
mock_openเป็นส่วนหนึ่งของmockกรอบงานและใช้งานง่ายมาก patchใช้เป็นบริบทส่งคืนวัตถุที่ใช้ในการแทนที่หนึ่ง patched: คุณสามารถใช้มันเพื่อให้การทดสอบของคุณง่ายขึ้น
ใช้แทนbuiltins__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ไม่ได้เป็นส่วนหนึ่งunittestและคุณควรแก้ไข__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")
ถ้าคุณจะใช้patchเป็นมัณฑนากรโดยใช้mock_open()ผลลัพธ์ของเนื่องจากnew patchอาร์กิวเมนต์อาจแปลกไปหน่อย
ในกรณีนี้จะดีกว่าที่จะใช้new_callable patch's อาร์กิวเมนต์และจำไว้ว่าทุกข้อโต้แย้งพิเศษที่patchไม่ได้ใช้งานจะถูกส่งผ่านไปยังnew_callableฟังก์ชั่นที่อธิบายไว้ในเอกสารpatch
patch () รับข้อโต้แย้งคำหลักโดยพลการ สิ่งเหล่านี้จะถูกส่งไปยังจำลอง (หรือ new_callable) ในการก่อสร้าง
เช่นรุ่นตกแต่งสำหรับ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")
โปรดจำไว้ว่าในกรณีนี้patchจะเพิ่มวัตถุจำลองเป็นอาร์กิวเมนต์ของคุณทดสอบฟังก์ชั่น
with patch("builtins.open", mock_open(read_data="data")) as mock_file:แปลงเป็นไวยากรณ์มัณฑนากร? ฉันพยายามแล้ว แต่ฉันไม่แน่ใจว่าสิ่งที่ฉันต้องผ่าน@patch("builtins.open", ...) เป็นข้อโต้แย้งที่สอง
return_valueของmock_openเข้าไปในวัตถุจำลองอื่นและยืนยันที่สองของการเยาะเย้ยreturn_value) แต่มันทำงานโดยการเพิ่มเป็นmock_open new_callable
sixโมดูลเพื่อให้มีmockโมดูลที่สอดคล้องกัน แต่ฉันไม่รู้ว่ามันแมปbuiltinsในโมดูลทั่วไปด้วยหรือไม่
ด้วย mock เวอร์ชันล่าสุดคุณสามารถใช้เครื่องมือช่วยmock_open ที่มีประโยชน์จริงๆ:
mock_open (mock = ไม่มี, read_data = ไม่มี)
ฟังก์ชั่นตัวช่วยในการสร้างจำลองเพื่อแทนที่การใช้งานแบบเปิด มันทำงานสำหรับเปิดเรียกโดยตรงหรือใช้เป็นผู้จัดการบริบท
อาร์กิวเมนต์จำลองเป็นวัตถุจำลองเพื่อกำหนดค่า หากไม่มี (ค่าเริ่มต้น) จะมีการสร้าง MagicMock ให้กับคุณโดย API จะ จำกัด วิธีหรือคุณลักษณะที่มีอยู่ในการจัดการไฟล์มาตรฐาน
read_data เป็นสตริงสำหรับวิธีการอ่านของการจัดการไฟล์ที่จะกลับมา นี่คือสตริงว่างเปล่าโดยค่าเริ่มต้น
>>> 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โทรหลายสายได้อย่างไร?
handle.write.assert_any_call()ไว้ คุณยังสามารถใช้handle.write.call_args_listเพื่อรับสายแต่ละครั้งหากการสั่งซื้อมีความสำคัญ
m.return_value.write.assert_called_once_with('some stuff')ดีกว่า IMO หลีกเลี่ยงการลงทะเบียนสาย
Mock.call_args_listนั้นปลอดภัยกว่าการเรียกMock.assert_xxxวิธีการใด ๆ หากคุณสะกดคำผิดใด ๆ หลังซึ่งเป็นคุณลักษณะของเยาะเย้ยพวกเขาจะผ่านไปอย่างเงียบ ๆ
ในการใช้mock_openสำหรับไฟล์อย่างง่ายread()(ตัวอย่าง mock_open ดั้งเดิมที่กำหนดไว้แล้วในหน้านี้เหมาะสำหรับการเขียนมากขึ้น):
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()
หมายเหตุตามเอกสารสำหรับ mock_open สิ่งนี้มีไว้เฉพาะread()ดังนั้นจะไม่ทำงานกับรูปแบบทั่วไปfor line in fเช่น
ใช้ python 2.6.6 / จำลอง 1.0.1
for line in opened_file:ประเภทของรหัสได้ ฉันพยายามทดลองด้วย StringIO ที่ใช้การได้ซึ่งนำไปปฏิบัติ__iter__และใช้สิ่งนั้นแทนmy_textแต่ไม่มีโชค
read()เพื่อไม่ให้ทำงานในfor line in opened_fileกรณีของคุณ ฉันได้แก้ไขโพสต์เพื่อชี้แจง
for line in f:สนับสนุนสามารถทำได้โดยการเยาะเย้ยค่าตอบแทนของopen()เป็นวัตถุ StringIO แทน
with open("any_string") as f: print f.read()
คำตอบยอดนิยมมีประโยชน์ แต่ฉันขยายมันเล็กน้อย
หากคุณต้องการตั้งค่าของวัตถุไฟล์ ( fในas f) ตามอาร์กิวเมนต์ที่ส่งไปopen()ที่นี่เป็นวิธีหนึ่งในการดำเนินการ:
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
โดยทั่วไปแล้วopen()จะส่งคืนวัตถุและwithจะเรียก__enter__()วัตถุนั้น
ในการเยาะเย้ยอย่างถูกต้องเราต้องเยาะเย้ยopen()เพื่อกลับวัตถุจำลอง วัตถุจำลองนั้นควรเยาะเย้ยการ__enter__()เรียกใช้มัน ( MagicMockจะทำสิ่งนี้ให้เรา) เพื่อคืนค่าวัตถุข้อมูล / ไฟล์จำลองที่เราต้องการ (ด้วยเหตุนี้mm.__enter__.return_value) การทำเช่นนี้ด้วย 2 mocks ตามวิธีด้านบนช่วยให้เราสามารถจับข้อโต้แย้งที่ส่งผ่านopen()และส่งผ่านไปยังdo_something_with_dataวิธีการของเรา
ฉันส่งไฟล์จำลองทั้งหมดเป็นสตริงไปopen()และdo_something_with_dataดูเหมือนว่า:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
สิ่งนี้จะแปลงสตริงเป็นรายการเพื่อให้คุณสามารถทำสิ่งต่อไปนี้ได้เช่นเดียวกับไฟล์ปกติ:
for line in file:
#do action
__enter__หรือไม่? แน่นอนว่ามันดูเหมือนแฮ็คมากกว่าวิธีที่แนะนำ
ฉันอาจจะสายไปหน่อยสำหรับเกม แต่สิ่งนี้ใช้ได้กับฉันเมื่อโทรopenในโมดูลอื่นโดยไม่ต้องสร้างไฟล์ใหม่
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")
โดยการปรับปรุงopenฟังก์ชั่นภายใน__builtin__โมดูลกับของmock_open()ฉันฉันสามารถเยาะเย้ยการเขียนไฟล์โดยไม่ต้องสร้าง
หมายเหตุ: หากคุณกำลังใช้โมดูลที่ใช้ cython หรือโปรแกรมของคุณขึ้นอยู่กับ cython ในทางใดทางหนึ่งคุณจะต้องนำเข้าโมดูลcython__builtin__โดยรวมimport __builtin__ที่ด้านบนของไฟล์ของคุณ คุณจะไม่สามารถเยาะเย้ยถ้วนทั่ว__builtin__หากคุณใช้ cython
import __builtin__ในโมดูลทดสอบของฉัน บทความนี้ช่วยชี้แจงว่าทำไมเทคนิคนี้ถึงใช้ได้เช่นเดียวกับที่ทำ: ichimonji10.name/blog/6
สิ่งนี้ทำงานได้สำหรับแพตช์เพื่ออ่านการกำหนดค่า json
class ObjectUnderTest:
def __init__(self, filename: str):
with open(filename, 'r') as f:
dict_content = json.load(f)
วัตถุที่เย้ยหยันคือวัตถุ io.TextIOWrapper ที่ส่งคืนโดยฟังก์ชัน 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):
หากคุณไม่ต้องการไฟล์ใด ๆ เพิ่มเติมคุณสามารถตกแต่งวิธีทดสอบได้:
@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
result = testeme()
assert result == "data"