ฉันจะทดสอบโค้ดต่อไปนี้ด้วย 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"