unittest.mock --- کتابخانهی شیء ماک¶
اضافه شده در نسخهی 3.3.
کد منبع: Lib/unittest/mock.py
unittest.mock کتابخانهای برای آزمون در پایتون است. این کتابخانه به شما امکان میدهد بخشهایی از سیستم تحت آزمون خود را با اشیای ماک جایگزین کنید و ادعاهایی درباره چگونگی استفاده از آنها مطرح کنید.
unittest.mock یک کلاس Mock اصلی ارائه میدهد که نیاز به ایجاد قطعههای جایگزین (stubs) زیادی در سراسر بدنه آزمون شما را برطرف میکند. پس از انجام یک عمل، میتوانید ادعاهایی دربارهی اینکه کدام متدها / ویژگیها استفادهشدهاند و با چه آرگومانهایی فراخوانیشدهاند، مطرح کنید. همچنین میتوانید مقادیر بازگشتی را مشخص کنید و ویژگیهای مورد نیاز را به روش عادی تنظیم کنید.
علاوه بر این، ماک یک دکوراتور patch() ارائه میدهد که وصله کردن ویژگیهای سطح ماژول و کلاس را در محدوده یک آزمون مدیریت میکند، به همراه sentinel برای ایجاد اشیای یکتا. برای چند مثال از نحوه استفاده از Mock، MagicMock و patch()، به quick guide مراجعه کنید.
ماک برای استفاده با unittest طراحی شده است و بهجای الگوی «ضبط -> بازپخش» که در بسیاری از چارچوبهای ماک استفاده میشود، بر الگوی «عمل -> ادعا» مبتنی است.
یک بکپورت از unittest.mock برای نسخههای پیشین پایتون وجود دارد که بهعنوان mock در PyPI در دسترس است.
راهنمای سریع¶
اشیای Mock و MagicMock همه ویژگیها و متدها را هنگامی که به آنها دسترسی پیدا میکنید ایجاد میکنند و جزئیات نحوه استفاده از خود را ذخیره میکنند. شما میتوانید آنها را پیکربندی کنید تا مقادیر بازگشتی را مشخص کنید یا ویژگیهای در دسترس را محدود کنید، و سپس ادعاهایی درباره نحوه استفاده از آنها مطرح کنید:
>>> from unittest.mock import MagicMock
>>> thing = ProductionClass()
>>> thing.method = MagicMock(return_value=3)
>>> thing.method(3, 4, 5, key='value')
3
>>> thing.method.assert_called_with(3, 4, 5, key='value')
side_effect به شما اجازه میدهد اثرات جانبی انجام دهید، از جمله پرتاب یک استثنا هنگامی که یک ماک فراخوانی میشود:
>>> from unittest.mock import Mock
>>> mock = Mock(side_effect=KeyError('foo'))
>>> mock()
Traceback (most recent call last):
...
KeyError: 'foo'
>>> values = {'a': 1, 'b': 2, 'c': 3}
>>> def side_effect(arg):
... return values[arg]
...
>>> mock.side_effect = side_effect
>>> mock('a'), mock('b'), mock('c')
(1, 2, 3)
>>> mock.side_effect = [5, 4, 3, 2, 1]
>>> mock(), mock(), mock()
(5, 4, 3)
ماک راههای دیگر بسیاری دارد که میتوانید آن را پیکربندی کنید و رفتار آن را کنترل نمایید. برای مثال، آرگومان spec ماک را پیکربندی میکند تا مشخصات خود را از شیء دیگری بگیرد. تلاش برای دسترسی به ویژگیها یا متدهایی در ماک که در spec وجود ندارند، با AttributeError شکست خواهد خورد.
دکوراتور patch() / مدیر زمینه، ماک کردن کلاسها یا اشیاء در یک ماژول تحت آزمون را آسان میسازد. شیءای که شما مشخص میکنید، در طول آزمون با یک ماک (یا شیء دیگر) جایگزین میشود و هنگامی که آزمون پایان مییابد بازگردانده میشود:
>>> from unittest.mock import patch
>>> @patch('module.ClassName2')
... @patch('module.ClassName1')
... def test(MockClass1, MockClass2):
... module.ClassName1()
... module.ClassName2()
... assert MockClass1 is module.ClassName1
... assert MockClass2 is module.ClassName2
... assert MockClass1.called
... assert MockClass2.called
...
>>> test()
توجه
هنگامی که دکوراتورهای patch را تودرتو میکنید، ماکها به همان ترتیبی که اعمال میشوند، به تابع دکوراتشده داده میشوند (ترتیب عادی Python برای اعمال دکوراتورها). این یعنی از پایین به بالا، بنابراین در مثال بالا، ماک برای module.ClassName1 ابتدا داده میشود.
با patch()، مهم است که اشیاء را در فضای نامی که در آن جستجو میشوند وصله (patch) کنید. این کار معمولاً ساده است، اما برای یک راهنمای سریع محل وصلهکردن را بخوانید.
علاوه بر استفاده بهعنوان دکوراتور، patch() میتواند بهعنوان مدیر زمینه در یک دستور with استفاده شود:
>>> with patch.object(ProductionClass, 'method', return_value=None) as mock_method:
... thing = ProductionClass()
... thing.method(1, 2, 3)
...
>>> mock_method.assert_called_once_with(1, 2, 3)
همچنین patch.dict() برای تنظیم مقادیر در یک دیکشنری فقط در طول یک محدوده و بازگرداندن دیکشنری به حالت اصلی خود هنگام پایان آزمون وجود دارد:
>>> foo = {'key': 'value'}
>>> original = foo.copy()
>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
... assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == original
Mock از ماک کردن متدهای جادویی پایتون پشتیبانی میکند. سادهترین راه برای استفاده از متدهای ویژه، استفاده از کلاس MagicMock است. این کلاس به شما امکان میدهد کارهایی مانند موارد زیر را انجام دهید:
>>> mock = MagicMock()
>>> mock.__str__.return_value = 'foobarbaz'
>>> str(mock)
'foobarbaz'
>>> mock.__str__.assert_called_with()
ماک به شما اجازه میدهد توابع (یا دیگر نمونههای ماک) را به متدهای جادویی اختصاص دهید و آنها بهدرستی فراخوانی میشوند. کلاس MagicMock صرفاً گونهای از ماک است که تمام متدهای ویژه آن از پیش برای شما ایجاد شدهاند (البته بههرحال همهی متدهای مفید).
در زیر مثالی از استفاده از متدهای جادویی با کلاس معمولی Mock آمده است:
>>> mock = Mock()
>>> mock.__str__ = Mock(return_value='wheeeeee')
>>> str(mock)
'wheeeeee'
برای اطمینان از اینکه اشیای ماک در آزمونهای شما همان API اشیایی را دارند که جایگزین آنها میشوند، میتوانید از auto-speccing استفاده کنید. Auto-speccing را میتوان از طریق آرگومان autospec در patch، یا تابع create_autospec() انجام داد. Auto-speccing اشیای ماکی ایجاد میکند که همان ویژگیها و متدهای اشیایی را دارند که جایگزین آنها میشوند، و همه توابع و متدها (از جمله سازندهها) همان امضای فراخوانی شیء واقعی را دارند.
این موضوع تضمین میکند که ماکهای شما در صورت استفادهی نادرست، به همان شیوهای که آپکد شما شکست میخورد، شکست خواهند خورد:
>>> from unittest.mock import create_autospec
>>> def function(a, b, c):
... pass
...
>>> mock_function = create_autospec(function, return_value='fishy')
>>> mock_function(1, 2, 3)
'fishy'
>>> mock_function.assert_called_once_with(1, 2, 3)
>>> mock_function('wrong arguments')
Traceback (most recent call last):
...
TypeError: missing a required argument: 'b'
همچنین میتوان از create_autospec() برای کلاسها نیز استفاده کرد، که در این حالت امضای متد __init__ را کپی میکند، و برای اشیاء قابل فراخوانی، که در این حالت امضای متد __call__ را کپی میکند.
کلاس ماک¶
Mock یک شیء ماک انعطافپذیر است که برای جایگزینی استفاده از جایگزینهای موقت (stubs) و جایگزینهای آزمون (test doubles) در سراسر کد شما در نظر گرفته شده است. ماکها فراخوانیپذیر هستند و هنگامی که به ویژگیهای آنها دسترسی پیدا میکنید، آن ویژگیها را بهعنوان ماکهای جدید ایجاد میکنند [1]. دسترسی به همان ویژگی همیشه همان ماک را برمیگرداند. ماکها نحوه استفاده شما از آنها را ثبت میکنند و به شما امکان میدهند درباره آنچه کد شما با آنها انجام داده است، ادعاهایی مطرح کنید.
MagicMock یک زیرکلاس از Mock است که تمام متدهای جادویی آن از پیش ساختهشدهاند و آماده استفاده هستند. همچنین گونههای غیرفراخوانیپذیر نیز وجود دارند که هنگام ماککردن اشیایی که فراخوانیپذیر نیستند، مفید هستند: NonCallableMock و NonCallableMagicMock
دکوراتورهای patch() جایگزینی موقت کلاسها در یک ماژول خاص با یک شیء Mock را آسان میکنند. بهطور پیشفرض، patch() یک MagicMock برای شما ایجاد میکند. شما میتوانید با استفاده از آرگومان new_callable در patch()، یک کلاس جایگزین برای Mock را مشخص کنید.
- class unittest.mock.Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)¶
یک شیء
Mockجدید ایجاد کنید.Mockچند آرگومان اختیاری میگیرد که رفتار شیء Mock را مشخص میکنند:spec: این میتواند یک فهرست از رشتهها یا یک شیء موجود (یک کلاس یا نمونه) باشد که بهعنوان مشخصات برای شیء ماک عمل میکند. اگر یک شیء را بهعنوان ورودی بدهید، یک فهرست از رشتهها با فراخوانی dir روی آن شیء تشکیل میشود (بهجز ویژگیها و متدهای ویژه پشتیبانینشده). دسترسی به هر ویژگی که در این فهرست نباشد، باعث پرتاب
AttributeErrorمیشود.اگر spec یک شیء باشد (نه فهرستی از رشتهها)، آنگاه
__class__کلاس شیء spec را برمیگرداند. این به ماکها اجازه میدهد آزمونهایisinstance()را بگذرانند.spec_set: گونهای سختگیرانهتر از spec. در صورت استفاده، تلاش برای set کردن یا دریافت یک ویژگی روی ماک که آن ویژگی روی شیء ارسالشده بهعنوان spec_set وجود ندارد، باعث پرتاب
AttributeErrorمیشود.side_effect: تابعی که هرگاه ماک فراخوانی شود، فراخوانی میشود. ویژگی
side_effectرا ببینید. برای پرتاب استثناها یا تغییر مقادیر بازگشتی بهصورت پویا مفید است. این تابع با همان آرگومانهای ماک فراخوانی میشود، و مگر اینکهDEFAULTرا برگرداند، مقدار بازگشتی این تابع بهعنوان مقدار بازگشتی استفاده میشود.بهعنوان جایگزین، side_effect میتواند یک کلاس یا نمونه استثنا باشد. در این صورت، هنگام فراخوانی ماک، استثنا پرتاب میشود.
اگر side_effect یک پیمایشپذیر باشد، هر فراخوانی ماک مقدار بعدی را از آن پیمایشپذیر برمیگرداند.
میتوان یک side_effect را با تنظیم آن به
Noneپاک کرد.return_value: مقداری که هنگام فراخوانی ماک برگردانده میشود. بهطور پیشفرض، این یک Mock جدید است (که در اولین دسترسی ایجاد میشود). ویژگی
return_valueرا ببینید.unsafe: بهطور پیشفرض، دسترسی به هر ویژگی که نام آن با assert، assret، asert، aseert یا assrt آغاز میشود، باعث پرتاب استثنای
AttributeErrorمیشود. ارسالunsafe=Trueدسترسی به این ویژگیها را مجاز میکند.اضافه شده در نسخهی 3.5.
wraps: آیتمی که شیء ماک باید آن را پوشش دهد. اگر wraps برابر
Noneنباشد، فراخوانی Mock فراخوانی را به شیء پوششدادهشده منتقل میکند (نتیجه واقعی را برمیگرداند). دسترسی به ویژگی روی ماک، یک شیء Mock برمیگرداند که ویژگی متناظر شیء پوششدادهشده را پوشش میدهد (بنابراین تلاش برای دسترسی به ویژگیای که وجود ندارد، باعث پرتابAttributeErrorمیشود).اگر برای ماک یک return_value صریح تنظیم شده باشد، فراخوانیها به شیء پوشیدهشده منتقل نمیشوند و در عوض return_value برگردانده میشود.
name: اگر ماک دارای نام باشد، از آن نام در repr ماک استفاده میشود. این موضوع میتواند برای اشکالزدایی مفید باشد. نام به ماکهای فرزند منتقل میشود.
همچنین میتوان ماکها را با آرگومانهای کلیدواژهای دلخواه فراخوانی کرد. این آرگومانها برای تنظیم ویژگیهای ماک پس از ایجاد آن استفاده میشوند. برای جزئیات، متد
configure_mock()را ببینید.- assert_called()¶
ادعا کنید که ماک حداقل یک بار فراخوانیشده است.
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called()
اضافه شده در نسخهی 3.6.
- assert_called_once()¶
تأیید کنید که ماک دقیقاً یک بار فراخوانی شده است.
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called_once() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called_once() Traceback (most recent call last): ... AssertionError: Expected 'method' to have been called once. Called 2 times. Calls: [call(), call()].
اضافه شده در نسخهی 3.6.
- assert_called_with(*args, **kwargs)¶
این متد روش مناسبی برای تأیید این است که آخرین فراخوانی بهشکل خاصی انجام شده است:
>>> mock = Mock() >>> mock.method(1, 2, 3, test='wow') <Mock name='mock.method()' id='...'> >>> mock.method.assert_called_with(1, 2, 3, test='wow')
- assert_called_once_with(*args, **kwargs)¶
تأیید کنید که ماک دقیقاً یک بار فراخوانی شده است و آن فراخوانی با آرگومانهای مشخصشده بوده است.
>>> mock = Mock(return_value=None) >>> mock('foo', bar='baz') >>> mock.assert_called_once_with('foo', bar='baz') >>> mock('other', bar='values') >>> mock.assert_called_once_with('other', bar='values') Traceback (most recent call last): ... AssertionError: Expected 'mock' to be called once. Called 2 times. Calls: [call('foo', bar='baz'), call('other', bar='values')].
- assert_any_call(*args, **kwargs)¶
ادعا میکند که ماک با آرگومانهای مشخصشده فراخوانی شده است.
این ادعا (assert) در صورتی موفق میشود که ماک تاکنون فراخوانی شده باشد، برخلاف
assert_called_with()وassert_called_once_with()که فقط در صورتی موفق میشوند که فراخوانی، آخرین فراخوانی باشد، و در موردassert_called_once_with()باید تنها فراخوانی نیز باشد.>>> mock = Mock(return_value=None) >>> mock(1, 2, arg='thing') >>> mock('some', 'thing', 'else') >>> mock.assert_any_call(1, 2, arg='thing')
- assert_has_calls(calls, any_order=False)¶
ادعا میکند که ماک با فراخوانیهای مشخصشده فراخوانی شده است. فهرست
mock_callsبرای فراخوانیها بررسی میشود.اگر any_order نادرست باشد، فراخوانیها باید متوالی باشند. ممکن است فراخوانیهای اضافی قبل یا بعد از فراخوانیهای مشخصشده وجود داشته باشد.
اگر any_order درست باشد، فراخوانیها میتوانند به هر ترتیبی باشند، اما همه باید در
mock_callsظاهر شوند.>>> mock = Mock(return_value=None) >>> mock(1) >>> mock(2) >>> mock(3) >>> mock(4) >>> calls = [call(2), call(3)] >>> mock.assert_has_calls(calls) >>> calls = [call(4), call(2), call(3)] >>> mock.assert_has_calls(calls, any_order=True)
- assert_not_called()¶
ادعا کنید که ماک هرگز فراخوانی نشده است.
>>> m = Mock() >>> m.hello.assert_not_called() >>> obj = m.hello() >>> m.hello.assert_not_called() Traceback (most recent call last): ... AssertionError: Expected 'hello' to not have been called. Called 1 times. Calls: [call()].
اضافه شده در نسخهی 3.5.
- reset_mock(*, return_value=False, side_effect=False)¶
متد reset_mock تمام ویژگیهای فراخوانی یک شیء ماک را بازنشانی میکند:
>>> mock = Mock(return_value=None) >>> mock('hello') >>> mock.called True >>> mock.reset_mock() >>> mock.called False
این میتواند در مواردی مفید باشد که میخواهید مجموعهای از ادعاها را که از همان شیء استفادهی مجدد میکنند، مطرح کنید.
پارامتر return_value وقتی روی
Trueتنظیم شود،return_valueرا بازنشانی میکند:>>> mock = Mock(return_value=5) >>> mock('hello') 5 >>> mock.reset_mock(return_value=True) >>> mock('hello') <Mock name='mock()' id='...'>
پارامتر side_effect هنگامی که روی
Trueتنظیم شود،side_effectرا بازنشانی میکند:>>> mock = Mock(side_effect=ValueError) >>> mock('hello') Traceback (most recent call last): ... ValueError >>> mock.reset_mock(side_effect=True) >>> mock('hello') <Mock name='mock()' id='...'>
توجه داشته باشید که
reset_mock()بهطور پیشفرضreturn_value،side_effectیا هیچکدام از ویژگیهای فرزند را که با انتساب معمولی تنظیم کردهاید، پاک نمیکند.ماکهای فرزند نیز بازنشانی میشوند.
تغییر یافته در نسخهی 3.6: دو آرگومان فقطکلیدواژهای به تابع reset_mock افزوده شد.
- mock_add_spec(spec, spec_set=False)¶
مشخصات را به یک ماک اضافه کنید. spec میتواند یک شیء یا فهرستی از رشتهها باشد. فقط ویژگیهای موجود در spec را میتوان بهعنوان ویژگی از ماک واکشی کرد.
اگر spec_set درست باشد، تنها ویژگیهای موجود در spec قابل تنظیم هستند.
- attach_mock(mock, attribute)¶
یک ماک را بهعنوان ویژگی این ماک متصل کنید و نام و والد آن را جایگزین نمایید. فراخوانیهای ماک متصلشده در ویژگیهای
method_callsوmock_callsاین ماک ثبت خواهند شد.
- configure_mock(**kwargs)¶
ویژگیهای ماک را از طریق آرگومانهای کلیدواژهای تنظیم کنید.
میتوان ویژگیها بههمراه مقادیر بازگشتی و عوارض جانبی را روی ماکهای فرزند با استفاده از نماد نقطهای استاندارد و واگشایی یک دیکشنری در فراخوانی متد تنظیم کرد:
>>> mock = Mock() >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} >>> mock.configure_mock(**attrs) >>> mock.method() 3 >>> mock.other() Traceback (most recent call last): ... KeyError
همین مورد را میتوان در فراخوانی سازندهی ماکها به دست آورد:
>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} >>> mock = Mock(some_attribute='eggs', **attrs) >>> mock.some_attribute 'eggs' >>> mock.method() 3 >>> mock.other() Traceback (most recent call last): ... KeyError
configure_mock()برای آسانتر کردن انجام پیکربندی پس از ایجاد ماک وجود دارد.
- __dir__()¶
اشیای
Mockنتایجdir(some_mock)را به نتایج مفید محدود میکنند. برای ماکهای دارای spec، این شامل تمام ویژگیهای مجاز برای ماک میشود.برای آگاهی از عملکرد این پالایش و چگونگی غیرفعال کردن آن،
FILTER_DIRرا ببینید.
- _get_child_mock(**kw)¶
ماکهای فرزند را برای ویژگیها و مقدار بازگشتی ایجاد میکند. بهطور پیشفرض، ماکهای فرزند از همان نوع والد خواهند بود. زیرکلاسهای Mock ممکن است بخواهند این را بازنویسی کنند تا نحوهی ساخت ماکهای فرزند را سفارشیسازی کنند.
برای ماکهای غیرفراخوانیپذیر، از نوع فراخوانیپذیر استفاده خواهد شد (بهجای هر زیرکلاس سفارشی).
- called¶
یک بولی که نشان میدهد شیء ماک فراخوانی شده است یا خیر:
>>> mock = Mock(return_value=None) >>> mock.called False >>> mock() >>> mock.called True
- call_count¶
عدد صحیحی که به شما میگوید شیء ماک چند بار فراخوانی شده است:
>>> mock = Mock(return_value=None) >>> mock.call_count 0 >>> mock() >>> mock() >>> mock.call_count 2
- return_value¶
این را تنظیم کنید تا مقدار برگرداندهشده با فراخوانی ماک پیکربندی شود:
>>> mock = Mock() >>> mock.return_value = 'fish' >>> mock() 'fish'
مقدار بازگشتی پیشفرض یک شیء ماک است و شما میتوانید آن را به روش معمول پیکربندی کنید:
>>> mock = Mock() >>> mock.return_value.attribute = sentinel.Attribute >>> mock.return_value() <Mock name='mock()()' id='...'> >>> mock.return_value.assert_called_with()
return_valueرا نیز میتوان در سازنده تنظیم کرد:>>> mock = Mock(return_value=3) >>> mock.return_value 3 >>> mock() 3
- side_effect¶
این میتواند یک تابع برای فراخوانی هنگام فراخوانی ماک، یک پیمایشپذیر، یا یک استثنا (کلاس یا نمونه) برای پرتاب باشد.
اگر تابعی را بهعنوان ورودی بدهید، آن تابع با همان آرگومانهایی که ماک دریافت میکند فراخوانی میشود و مگر آنکه تابع شیء یکتای
DEFAULTرا برگرداند، فراخوانی ماک هر مقداری را که تابع برگرداند برمیگرداند. اگر تابعDEFAULTرا برگرداند، آنگاه ماک مقدار معمول خود را (ازreturn_value) برمیگرداند.اگر یک پیمایشپذیر به آن بدهید، از آن برای گرفتن یک پیمایشگر استفاده میشود که باید در هر فراخوانی یک مقدار تولید کند. این مقدار میتواند یک نمونه استثنا باشد که پرتاب میشود، یا مقداری که از فراخوانی ماک بازگردانده میشود (مدیریت
DEFAULTمشابه حالت تابع است).مثالی از یک ماک که استثنایی را پرتاب میکند (برای آزمون مدیریت استثنا در یک API):
>>> mock = Mock() >>> mock.side_effect = Exception('Boom!') >>> mock() Traceback (most recent call last): ... Exception: Boom!
استفاده از
side_effectبرای برگرداندن دنبالهای از مقادیر:>>> mock = Mock() >>> mock.side_effect = [3, 2, 1] >>> mock(), mock(), mock() (3, 2, 1)
استفاده از یک فراخوانیپذیر :
>>> mock = Mock(return_value=3) >>> def side_effect(*args, **kwargs): ... return DEFAULT ... >>> mock.side_effect = side_effect >>> mock() 3
side_effectرا میتوان در سازنده تنظیم کرد. در اینجا مثالی آمده است که یک واحد به مقداری که ماک با آن فراخوانی میشود میافزاید و آن را برمیگرداند:>>> side_effect = lambda value: value + 1 >>> mock = Mock(side_effect=side_effect) >>> mock(3) 4 >>> mock(-8) -7
تنظیم
side_effectرویNoneآن را پاک میکند:>>> m = Mock(side_effect=KeyError, return_value=3) >>> m() Traceback (most recent call last): ... KeyError >>> m.side_effect = None >>> m() 3
- call_args¶
این مقدار یا
Noneاست (اگر ماک فراخوانینشده باشد)، یا آرگومانهایی که ماک آخرین بار با آنها فراخوانی شده است. این مقدار در قالب یک تاپل خواهد بود: عضو اول، که میتوان از طریق ویژگیargsنیز به آن دسترسی داشت، آرگومانهای جایگاهی است که ماک با آنها فراخوانی شده است (یا یک تاپل خالی) و عضو دوم، که میتوان از طریق ویژگیkwargsنیز به آن دسترسی داشت، آرگومانهای کلیدواژهای است (یا یک دیکشنری خالی).>>> mock = Mock(return_value=None) >>> print(mock.call_args) None >>> mock() >>> mock.call_args call() >>> mock.call_args == () True >>> mock(3, 4) >>> mock.call_args call(3, 4) >>> mock.call_args == ((3, 4),) True >>> mock.call_args.args (3, 4) >>> mock.call_args.kwargs {} >>> mock(3, 4, 5, key='fish', next='w00t!') >>> mock.call_args call(3, 4, 5, key='fish', next='w00t!') >>> mock.call_args.args (3, 4, 5) >>> mock.call_args.kwargs {'key': 'fish', 'next': 'w00t!'}
call_args، بههمراه اعضای فهرستهایcall_args_list،method_callsوmock_calls، اشیایcallهستند. اینها تاپل هستند، بنابراین میتوان آنها را واگشایی کرد تا به آرگومانهای جداگانه دسترسی پیدا کرد و ادعاهای پیچیدهتری مطرح کرد. به فراخوانیها بهعنوان تاپل مراجعه کنید.تغییر یافته در نسخهی 3.8: ویژگیهای
argsوkwargsافزوده شدند.
- call_args_list¶
این فهرستی از تمام فراخوانیهای انجامشده به شیء ماک به ترتیب است (بنابراین طول فهرست برابر با تعداد دفعاتی است که شیء ماک فراخوانی شده است). پیش از انجام هرگونه فراخوانی، این فهرست خالی است. میتوان از شیء
callبرای ساختن آسان فهرستهایی از فراخوانیها جهت مقایسه باcall_args_listاستفاده کرد.>>> mock = Mock(return_value=None) >>> mock() >>> mock(3, 4) >>> mock(key='fish', next='w00t!') >>> mock.call_args_list [call(), call(3, 4), call(key='fish', next='w00t!')] >>> expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)] >>> mock.call_args_list == expected True
اعضای
call_args_list، اشیایcallهستند. میتوانید آنها را بهصورت تاپل واگشایی کنید تا به آرگومانهای منفرد دسترسی پیدا کنید. به فراخوانیها بهصورت تاپل مراجعه کنید.
- method_calls¶
ماکها علاوه بر پیگیری فراخوانیهای خودشان، فراخوانیهای متدها و ویژگیها، و متدها و ویژگیهای آنها را نیز پیگیری میکنند:
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.property.method.attribute() <Mock name='mock.property.method.attribute()' id='...'> >>> mock.method_calls [call.method(), call.property.method.attribute()]
اعضای
method_callsاشیایcallهستند. میتوان آنها را بهصورت تاپل واگشایی کرد تا به آرگومانهای جداگانه دسترسی پیدا کرد. فراخوانیها بهصورت تاپل را ببینید.
- mock_calls¶
mock_callsهمه فراخوانیها به شیء ماک، متدهای آن، متدهای جادویی و ماکهای مقدار بازگشتی را ثبت میکند.>>> mock = MagicMock() >>> result = mock(1, 2, 3) >>> mock.first(a=3) <MagicMock name='mock.first()' id='...'> >>> mock.second() <MagicMock name='mock.second()' id='...'> >>> int(mock) 1 >>> result(1) <MagicMock name='mock()()' id='...'> >>> expected = [call(1, 2, 3), call.first(a=3), call.second(), ... call.__int__(), call()(1)] >>> mock.mock_calls == expected True
عناصر
mock_callsاشیایcallهستند. میتوانید اینها را بهصورت تاپل واگشایی کنید تا به تکتک آرگومانها دسترسی پیدا کنید. فراخوانیها بهصورت تاپل را ببینید.توجه
نحوهی ثبت
mock_callsبه این معناست که هرگاه فراخوانیهای تودرتو انجام شوند، پارامترهای فراخوانیهای بالادستی ثبت نمیشوند و بنابراین همیشه در مقایسه برابر خواهند بود:>>> mock = MagicMock() >>> mock.top(a=3).bottom() <MagicMock name='mock.top().bottom()' id='...'> >>> mock.mock_calls [call.top(a=3), call.top().bottom()] >>> mock.mock_calls[-1] == call.top(a=-1).bottom() True
- __class__¶
معمولاً ویژگی
__class__یک شیء، نوع آن را برمیگرداند. برای یک شیء ماک دارایspec، ویژگی__class__در عوض کلاس مشخصشده در spec را برمیگرداند. این امکان را به اشیاء ماک میدهد تا آزمونهایisinstance()را برای شیءای که جایگزین آن هستند / خود را بهجای آن جا میزنند، با موفقیت بگذرانند:>>> mock = Mock(spec=3) >>> isinstance(mock, int) True
__class__قابل انتساب است؛ این به یک ماک اجازه میدهد بدون اینکه شما را مجبور به استفاده از spec کند، بررسیisinstance()را بگذراند:>>> mock = Mock() >>> mock.__class__ = dict >>> isinstance(mock, dict) True
- class unittest.mock.NonCallableMock(spec=None, wraps=None, name=None, spec_set=None, **kwargs)¶
نسخهای غیرفراخوانیپذیر از
Mock. پارامترهای سازنده همان معنای پارامترهایMockرا دارند، بهجز return_value و side_effect که در یک ماک غیرفراخوانیپذیر معنایی ندارند.
اشیای ماک که از یک کلاس یا نمونه بهعنوان spec یا spec_set استفاده میکنند، میتوانند آزمونهای isinstance() را بگذرانند:
>>> mock = Mock(spec=SomeClass)
>>> isinstance(mock, SomeClass)
True
>>> mock = Mock(spec_set=SomeClass())
>>> isinstance(mock, SomeClass)
True
کلاسهای Mock از ماک کردن متدهای جادویی پشتیبانی میکنند. برای جزئیات کامل، متدهای جادویی را ببینید.
کلاسهای ماک و دکوراتورهای patch() همگی آرگومانهای کلیدواژهای دلخواه را برای پیکربندی میپذیرند. در دکوراتورهای patch()، کلیدواژهها به سازندهی ماکِ در حال ایجاد ارسال میشوند. آرگومانهای کلیدواژهای برای پیکربندی ویژگیهای ماک هستند:
>>> m = MagicMock(attribute=3, other='fish')
>>> m.attribute
3
>>> m.other
'fish'
مقدار بازگشتی و اثر جانبی ماکهای فرزند را میتوان به همین روش، با استفاده از نمادگذاری نقطهای تنظیم کرد. از آنجا که نمیتوانید از نامهای نقطهای مستقیماً در یک فراخوانی استفاده کنید، باید یک دیکشنری ایجاد کنید و آن را با استفاده از ** واگشایی کنید:
>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
...
KeyError
یک ماک فراخوانیپذیر که با یک spec (یا یک spec_set) ایجاد شده است، هنگام تطبیق فراخوانیها با ماک، امضای شیء مشخصات را دروننگری میکند. بنابراین، میتواند آرگومانهای فراخوانی واقعی را صرفنظر از اینکه بهصورت جایگاهی یا با نام ارسال شده باشند، تطبیق دهد:
>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, c=3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(1, 2, 3)
>>> mock.assert_called_with(a=1, b=2, c=3)
این موضوع به assert_called_with()، assert_called_once_with()، assert_has_calls() و assert_any_call() اعمال میشود. هنگام مشخصهدهی خودکار (Autospeccing)، این موضوع در مورد فراخوانیهای متد بر روی شیء ماک نیز اعمال میشود.
تغییر یافته در نسخهی 3.4: دروننگری امضا (signature introspection) برای اشیای ماک specced و autospecced افزوده شد.
- class unittest.mock.PropertyMock(*args, **kwargs)¶
ماکی که برای استفاده بهعنوان
property، یا descriptor دیگری، روی یک کلاس در نظر گرفته شده است.PropertyMockمتدهای__get__()و__set__()را فراهم میکند تا بتوانید یک مقدار بازگشتی را هنگام واکشی آن مشخص کنید.بازیابی یک نمونه
PropertyMockاز یک شیء، ماک را بدون آرگومان فراخوانی میکند. تنظیم آن، ماک را با مقداری که تنظیم میشود فراخوانی میکند.>>> class Foo: ... @property ... def foo(self): ... return 'something' ... @foo.setter ... def foo(self, value): ... pass ... >>> with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo: ... mock_foo.return_value = 'mockity-mock' ... this_foo = Foo() ... print(this_foo.foo) ... this_foo.foo = 6 ... mockity-mock >>> mock_foo.mock_calls [call(), call(6)]
به دلیل نحوهی ذخیرهسازی ویژگیهای ماک، نمیتوانید یک PropertyMock را بهطور مستقیم به یک شیء ماک متصل کنید. در عوض میتوانید آن را به شیء نوع ماک متصل کنید:
>>> m = MagicMock()
>>> p = PropertyMock(return_value=3)
>>> type(m).foo = p
>>> m.foo
3
>>> p.assert_called_once_with()
ملاحظه
اگر یک AttributeError از سوی PropertyMock پرتاب شود، بهعنوان یک توصیفگر مفقود تفسیر میشود و __getattr__() روی ماک والد فراخوانی خواهد شد:
>>> m = MagicMock()
>>> no_attribute = PropertyMock(side_effect=AttributeError)
>>> type(m).my_property = no_attribute
>>> m.my_property
<MagicMock name='mock.my_property' id='140165240345424'>
برای جزئیات، __getattr__() را ببینید.
- class unittest.mock.AsyncMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)¶
نسخهای ناهمگام از
MagicMock. شیءAsyncMockبهگونهای رفتار میکند که بهعنوان یک تابع ناهمگام شناخته میشود، و نتیجهی یک فراخوانی قابل await (awaitable) است.>>> mock = AsyncMock() >>> inspect.iscoroutinefunction(mock) True >>> inspect.isawaitable(mock()) True
نتیجهی
mock()یک تابع ناهمگام است که پس از اینکه await شد، نتیجهیside_effectیاreturn_valueرا خواهد داشت:اگر
side_effectیک تابع باشد، تابع ناهمگام نتیجهی آن تابع را برمیگرداند،اگر
side_effectیک استثنا باشد، تابع ناهمگام آن استثنا را پرتاب خواهد کرد،if
side_effectis an iterable, the async function will return the next value of the iterable, however, if the sequence of result is exhausted,StopAsyncIterationis raised immediately,اگر
side_effectتعریفنشده باشد، تابع ناهمگام مقدار تعریفشده توسطreturn_valueرا برمیگرداند، بنابراین، بهطور پیشفرض، تابع ناهمگام یک شیءAsyncMockجدید برمیگرداند.
تنظیم spec یک
MockیاMagicMockروی یک تابع ناهمگام باعث میشود پس از فراخوانی، یک شیء همروال برگردانده شود.>>> async def async_func(): pass ... >>> mock = MagicMock(async_func) >>> mock <MagicMock spec='function' id='...'> >>> mock() <coroutine object AsyncMockMixin._mock_call at ...>
اگر spec یک
Mock،MagicMockیاAsyncMockرا روی کلاسی دارای توابع ناهمگام و همگام تنظیم کنید، توابع همگام بهطور خودکار تشخیص داده میشوند و بهصورتMagicMock(اگر ماک والدAsyncMockیاMagicMockباشد) یاMock(اگر ماک والدMockباشد) تنظیم میشوند. تمام توابع ناهمگامAsyncMockخواهند بود.>>> class ExampleClass: ... def sync_foo(): ... pass ... async def async_foo(): ... pass ... >>> a_mock = AsyncMock(ExampleClass) >>> a_mock.sync_foo <MagicMock name='mock.sync_foo' id='...'> >>> a_mock.async_foo <AsyncMock name='mock.async_foo' id='...'> >>> mock = Mock(ExampleClass) >>> mock.sync_foo <Mock name='mock.sync_foo' id='...'> >>> mock.async_foo <AsyncMock name='mock.async_foo' id='...'>
اضافه شده در نسخهی 3.8.
- assert_awaited()¶
تأیید میکند که ماک حداقل یک بار await شده است. توجه داشته باشید که این از فراخوانی شدن شیء جدا است؛ باید از کلیدواژه
awaitاستفاده شود:>>> mock = AsyncMock() >>> async def main(coroutine_mock): ... await coroutine_mock ... >>> coroutine_mock = mock() >>> mock.called True >>> mock.assert_awaited() Traceback (most recent call last): ... AssertionError: Expected mock to have been awaited. >>> asyncio.run(main(coroutine_mock)) >>> mock.assert_awaited()
- assert_awaited_once()¶
ادعا کنید که ماک دقیقاً یکبار await شده است.
>>> mock = AsyncMock() >>> async def main(): ... await mock() ... >>> asyncio.run(main()) >>> mock.assert_awaited_once() >>> asyncio.run(main()) >>> mock.assert_awaited_once() Traceback (most recent call last): ... AssertionError: Expected mock to have been awaited once. Awaited 2 times.
- assert_awaited_with(*args, **kwargs)¶
تأیید میکند که آخرین await با آرگومانهای مشخصشده انجام شده است.
>>> mock = AsyncMock() >>> async def main(*args, **kwargs): ... await mock(*args, **kwargs) ... >>> asyncio.run(main('foo', bar='bar')) >>> mock.assert_awaited_with('foo', bar='bar') >>> mock.assert_awaited_with('other') Traceback (most recent call last): ... AssertionError: expected await not found. Expected: mock('other') Actual: mock('foo', bar='bar')
- assert_awaited_once_with(*args, **kwargs)¶
تصدیق میکند که ماک دقیقاً یکبار و با آرگومانهای مشخصشده await شده است.
>>> mock = AsyncMock() >>> async def main(*args, **kwargs): ... await mock(*args, **kwargs) ... >>> asyncio.run(main('foo', bar='bar')) >>> mock.assert_awaited_once_with('foo', bar='bar') >>> asyncio.run(main('foo', bar='bar')) >>> mock.assert_awaited_once_with('foo', bar='bar') Traceback (most recent call last): ... AssertionError: Expected mock to have been awaited once. Awaited 2 times.
- assert_any_await(*args, **kwargs)¶
ادعا میکند که ماک تاکنون با آرگومانهای مشخصشده await شده است.
>>> mock = AsyncMock() >>> async def main(*args, **kwargs): ... await mock(*args, **kwargs) ... >>> asyncio.run(main('foo', bar='bar')) >>> asyncio.run(main('hello')) >>> mock.assert_any_await('foo', bar='bar') >>> mock.assert_any_await('other') Traceback (most recent call last): ... AssertionError: mock('other') await not found
- assert_has_awaits(calls, any_order=False)¶
تأیید میکند که ماک با فراخوانیهای مشخصشده مورد await قرار گرفته است. فهرست
await_args_listبرای موارد await بررسی میشود.اگر any_order نادرست باشد، awaitها باید بهصورت متوالی انجام شوند. ممکن است فراخوانیهای اضافی پیش یا پس از awaitهای مشخصشده وجود داشته باشند.
اگر any_order درست باشد، awaitها میتوانند به هر ترتیبی باشند، اما همهی آنها باید در
await_args_listظاهر شوند.>>> mock = AsyncMock() >>> async def main(*args, **kwargs): ... await mock(*args, **kwargs) ... >>> calls = [call("foo"), call("bar")] >>> mock.assert_has_awaits(calls) Traceback (most recent call last): ... AssertionError: Awaits not found. Expected: [call('foo'), call('bar')] Actual: [] >>> asyncio.run(main('foo')) >>> asyncio.run(main('bar')) >>> mock.assert_has_awaits(calls)
- assert_not_awaited()¶
تأیید میکند که ماک هرگز await نشده است.
>>> mock = AsyncMock() >>> mock.assert_not_awaited()
- reset_mock(*args, **kwargs)¶
Mock.reset_mock()را ببینید. همچنینawait_countرا برابر ۰،await_argsرا برابر None قرار میدهد وawait_args_listرا پاک میکند.
- await_count¶
یک عدد صحیح که تعداد دفعات await شدن شیء ماک را پیگیری میکند.
>>> mock = AsyncMock() >>> async def main(): ... await mock() ... >>> asyncio.run(main()) >>> mock.await_count 1 >>> asyncio.run(main()) >>> mock.await_count 2
- await_args¶
این یا
Noneاست (اگر ماک await نشده باشد)، یا آرگومانهایی است که ماک آخرین بار با آنها await شده است. مانندMock.call_argsعمل میکند.>>> mock = AsyncMock() >>> async def main(*args): ... await mock(*args) ... >>> mock.await_args >>> asyncio.run(main('foo')) >>> mock.await_args call('foo') >>> asyncio.run(main('bar')) >>> mock.await_args call('bar')
- await_args_list¶
این فهرستی از تمام موارد await انجامشده روی شیء ماک بهترتیب است (بنابراین طول فهرست برابر با تعداد دفعاتی است که آن شیء await شده است). پیش از انجام هرگونه await، این فهرست خالی است.
>>> mock = AsyncMock() >>> async def main(*args): ... await mock(*args) ... >>> mock.await_args_list [] >>> asyncio.run(main('foo')) >>> mock.await_args_list [call('foo')] >>> asyncio.run(main('bar')) >>> mock.await_args_list [call('foo'), call('bar')]
- class unittest.mock.ThreadingMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, *, timeout=UNSET, **kwargs)¶
نسخهای از
MagicMockبرای آزمونهای چندنخی. شیءThreadingMockمتدهای اضافیای فراهم میکند تا بهجای ادعای فوری دربارهی یک فراخوانی، منتظر بمانید تا آن فراخوانی صورت گیرد.مهلت پیشفرض توسط آرگومان
timeoutمشخص میشود، یا در صورت تنظیمنشدن، توسط ویژگیThreadingMock.DEFAULT_TIMEOUT، که پیشفرض آن مسدودسازی (None) است.میتوانید با تنظیم
ThreadingMock.DEFAULT_TIMEOUT، مهلت زمانی پیشفرض سراسری را پیکربندی کنید.- wait_until_called(*, timeout=UNSET)¶
صبر میکند تا ماک فراخوانی شود.
اگر در زمان ایجاد ماک یک مهلت زمانی پاس داده شده باشد یا یک آرگومان مهلت زمانی به این تابع پاس داده شود، در صورتی که فراخوانی بهموقع انجام نشود، تابع استثنا
AssertionErrorرا پرتاب میکند.>>> mock = ThreadingMock() >>> thread = threading.Thread(target=mock) >>> thread.start() >>> mock.wait_until_called(timeout=1) >>> thread.join()
- wait_until_any_call_with(*args, **kwargs)¶
منتظر میماند تا ماک با آرگومانهای مشخصشده فراخوانی شود.
اگر در زمان ایجاد ماک، یک مهلت زمانی ارسال شده باشد، در صورتی که فراخوانی بهموقع انجام نشود، تابع یک
AssertionErrorپرتاب میکند.>>> mock = ThreadingMock() >>> thread = threading.Thread(target=mock, args=("arg1", "arg2",), kwargs={"arg": "thing"}) >>> thread.start() >>> mock.wait_until_any_call_with("arg1", "arg2", arg="thing") >>> thread.join()
- DEFAULT_TIMEOUT¶
مهلت زمانی پیشفرض سراسری بر حسب ثانیه برای ایجاد نمونههای
ThreadingMock.
اضافه شده در نسخهی 3.13.
فراخوانی¶
اشیاء ماک فراخوانیپذیر هستند. فراخوانی، مقدار تنظیمشده بهعنوان ویژگی return_value را برمیگرداند. مقدار بازگشتی پیشفرض، یک شیء ماک جدید است؛ این شیء نخستین باری که به مقدار بازگشتی دسترسی پیدا شود (چه بهصراحت و چه با فراخوانی ماک) ایجاد میشود — اما ذخیره میشود و هر بار همان شیء برگردانده میشود.
فراخوانیهای انجامشده روی شیء در ویژگیهایی مانند call_args و call_args_list ثبت خواهند شد.
اگر side_effect تنظیمشده باشد، پس از ثبت فراخوانی، فراخوانی خواهد شد، بنابراین اگر side_effect استثنایی پرتاب کند، فراخوانی همچنان ثبت میشود.
سادهترین راه برای وادار کردن یک ماک به پرتاب یک استثنا هنگام فراخوانی، این است که side_effect را یک کلاس یا نمونه استثنا قرار دهید:
>>> m = MagicMock(side_effect=IndexError)
>>> m(1, 2, 3)
Traceback (most recent call last):
...
IndexError
>>> m.mock_calls
[call(1, 2, 3)]
>>> m.side_effect = KeyError('Bang!')
>>> m('two', 'three', 'four')
Traceback (most recent call last):
...
KeyError: 'Bang!'
>>> m.mock_calls
[call(1, 2, 3), call('two', 'three', 'four')]
اگر side_effect یک تابع باشد، هر مقداری که آن تابع برمیگرداند همان مقداری است که فراخوانیهای ماک برمیگردانند. تابع side_effect با همان آرگومانهای ماک فراخوانی میشود. این امکان را به شما میدهد که مقدار بازگشتی فراخوانی را بهصورت پویا، بر اساس ورودی تغییر دهید:
>>> def side_effect(value):
... return value + 1
...
>>> m = MagicMock(side_effect=side_effect)
>>> m(1)
2
>>> m(2)
3
>>> m.mock_calls
[call(1), call(2)]
اگر میخواهید ماک همچنان مقدار برگشتی پیشفرض (یک ماک جدید) یا هر مقدار برگشتی تعیینشده را برگرداند، دو روش برای انجام این کار وجود دارد. یا return_value را از داخل side_effect برگردانید، یا DEFAULT را برگردانید:
>>> m = MagicMock()
>>> def side_effect(*args, **kwargs):
... return m.return_value
...
>>> m.side_effect = side_effect
>>> m.return_value = 3
>>> m()
3
>>> def side_effect(*args, **kwargs):
... return DEFAULT
...
>>> m.side_effect = side_effect
>>> m()
3
برای حذف side_effect و بازگشت به رفتار پیشفرض، side_effect را روی None تنظیم کنید:
>>> m = MagicMock(return_value=6)
>>> def side_effect(*args, **kwargs):
... return 3
...
>>> m.side_effect = side_effect
>>> m()
3
>>> m.side_effect = None
>>> m()
6
The side_effect can also be any iterable object. Repeated calls to the mock
will return values from the iterable (until the iterable is exhausted and
a StopIteration is raised):
>>> m = MagicMock(side_effect=[1, 2, 3])
>>> m()
1
>>> m()
2
>>> m()
3
>>> m()
Traceback (most recent call last):
...
StopIteration
اگر هر یک از آیتمهای پیمایشپذیر استثنا باشند، به جای برگردانده شدن، پرتاب خواهند شد:
>>> iterable = (33, ValueError, 66)
>>> m = MagicMock(side_effect=iterable)
>>> m()
33
>>> m()
Traceback (most recent call last):
...
ValueError
>>> m()
66
حذف ویژگیها¶
اشیاء ماک، ویژگیها را در صورت درخواست ایجاد میکنند. این موضوع به آنها امکان میدهد وانمود کنند که اشیایی از هر نوعی هستند.
ممکن است بخواهید یک شیء ماک در پاسخ به فراخوانی hasattr() مقدار False را برگرداند، یا هنگامی که یک ویژگی واکشی میشود، یک AttributeError پرتاب کند. میتوانید این کار را با ارائه یک شیء بهعنوان spec برای یک ماک انجام دهید، اما این روش همیشه مناسب نیست.
شما با حذف کردن ویژگیها، آنها را «مسدود» میکنید. پس از حذف، دسترسی به یک ویژگی باعث پرتاب AttributeError میشود.
>>> mock = MagicMock()
>>> hasattr(mock, 'm')
True
>>> del mock.m
>>> hasattr(mock, 'm')
False
>>> del mock.f
>>> mock.f
Traceback (most recent call last):
...
AttributeError: f
نامهای ماک و ویژگی name¶
از آنجا که "name" یک آرگومان برای سازندهی Mock است، اگر بخواهید شیء ماک شما دارای ویژگی "name" باشد، نمیتوانید آن را در زمان ایجاد بهسادگی به سازنده بدهید. دو جایگزین وجود دارد. یکی از گزینهها استفاده از configure_mock() است:
>>> mock = MagicMock()
>>> mock.configure_mock(name='my_name')
>>> mock.name
'my_name'
گزینه سادهتر این است که صرفاً ویژگی "name" را پس از ایجاد ماک تنظیم کنید:
>>> mock = MagicMock()
>>> mock.name = "foo"
پیوست کردن ماکها بهعنوان ویژگیها¶
هنگامی که یک ماک را بهعنوان ویژگی به ماک دیگری (یا بهعنوان مقدار بازگشتی) متصل میکنید، این ماک «فرزند» آن ماک میشود. فراخوانیهای فرزند در ویژگیهای method_calls و mock_calls والد ثبت میشوند. این موضوع برای پیکربندی ماکهای فرزند و سپس اتصال آنها به والد، یا برای اتصال ماکها به والدی که تمام فراخوانیهای فرزندان را ثبت میکند و به شما امکان میدهد دربارهی ترتیب فراخوانیها بین ماکها ادعا کنید، مفید است:
>>> parent = MagicMock()
>>> child1 = MagicMock(return_value=None)
>>> child2 = MagicMock(return_value=None)
>>> parent.child1 = child1
>>> parent.child2 = child2
>>> child1(1)
>>> child2(2)
>>> parent.mock_calls
[call.child1(1), call.child2(2)]
استثنای این مورد زمانی است که ماک دارای نام باشد. این به شما امکان میدهد که اگر به دلیلی نمیخواهید این اتفاق بیفتد، از «والدسازی» (parenting) جلوگیری کنید.
>>> mock = MagicMock()
>>> not_a_child = MagicMock(name='not-a-child')
>>> mock.attribute = not_a_child
>>> mock.attribute()
<MagicMock name='not-a-child()' id='...'>
>>> mock.mock_calls
[]
ماکهایی که توسط patch() برای شما ایجاد میشوند، بهطور خودکار نامگذاری میشوند. برای اتصال ماکهای دارای نام به یک والد، از متد attach_mock() استفاده میکنید:
>>> thing1 = object()
>>> thing2 = object()
>>> parent = MagicMock()
>>> with patch('__main__.thing1', return_value=None) as child1:
... with patch('__main__.thing2', return_value=None) as child2:
... parent.attach_mock(child1, 'child1')
... parent.attach_mock(child2, 'child2')
... child1('one')
... child2('two')
...
>>> parent.mock_calls
[call.child1('one'), call.child2('two')]
وصلهکنندهها¶
دکوراتورهای patch برای وصله کردن اشیاء فقط در محدوده تابعی که آن را دکور میکنند استفاده میشوند. آنها بهطور خودکار لغو وصله (unpatching) را برای شما مدیریت میکنند، حتی اگر استثناهایی پرتاب شوند. همچنین میتوان از همه این توابع در دستورات with یا بهعنوان دکوراتورهای کلاس استفاده کرد.
وصله¶
توجه
نکتهی کلیدی این است که وصلهکردن را در فضای نام درست انجام دهید. بخش where to patch را ببینید.
- unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)¶
patch()بهعنوان دکوراتور تابع، دکوراتور کلاس یا مدیر زمینه عمل میکند. در بدنه تابع یا دستور with، target با یک شیء new وصله میشود. هنگامی که تابع/دستور with به پایان میرسد، وصله لغو میشود.اگر new حذفشده باشد، چنانچه شیء وصلهشده یک تابع ناهمگام باشد، هدف با یک
AsyncMockو در غیر این صورت با یکMagicMockجایگزین میشود. اگرpatch()بهعنوان دکوراتور استفاده شود و new حذفشده باشد، ماک ایجادشده بهعنوان یک آرگومان اضافی به تابع دکوریتشده ارسال میشود. اگرpatch()بهعنوان مدیر زمینه استفاده شود، ماک ایجادشده توسط مدیر زمینه برگردانده میشود.target باید یک رشته در قالب
'package.module.ClassName'باشد. target ایمپورت میشود و شیء مشخصشده با شیء new جایگزین میشود، بنابراین target باید در محیطی که شماpatch()را از آن فراخوانی میکنید قابل ایمپورت باشد. هدف در زمان اجرای تابع دکوریتشده ایمپورت میشود، نه در زمان اعمال دکوراتور.اگر patch یک نمونه برای شما ایجاد کند، آرگومانهای کلیدواژهای spec و spec_set به
MagicMockارسال میشوند.علاوه بر این، میتوانید
spec=Trueیاspec_set=Trueرا ارسال کنید، که باعث میشود patch شیء ماکشده را بهعنوان شیء spec/spec_set ارسال کند.new_callable به شما امکان میدهد یک کلاس یا شیء فراخوانیپذیر متفاوت را مشخص کنید که برای ایجاد شیء new فراخوانی خواهد شد. بهطور پیشفرض برای توابع ناهمگام از
AsyncMockو برای بقیه ازMagicMockاستفاده میشود.شکل قدرتمندتری از مشخصات، autospec است. اگر
autospec=Trueرا تنظیم کنید، ماک با مشخصاتی از شیء جایگزینشده ایجاد میشود. همه ویژگیهای ماک نیز دارای مشخصات ویژگی متناظر از شیء جایگزینشده خواهند بود. متدها و توابعی که ماک شدهاند، آرگومانهایشان بررسی میشود و اگر با امضای نادرست فراخوانی شوند،TypeErrorپرتاب میکنند. برای ماکهایی که جایگزین یک کلاس میشوند، مقدار بازگشتی آنها («نمونه») همان مشخصات کلاس را خواهد داشت. تابعcreate_autospec()و مشخصهدهی خودکار (Autospeccing) را ببینید.بهجای
autospec=Trueمیتوانیدautospec=some_objectرا ارسال کنید تا بهجای شیءای که جایگزین میشود، از یک شیء دلخواه بهعنوان مشخصات استفاده کنید.بهطور پیشفرض
patch()در جایگزینی ویژگیهایی که وجود ندارند ناموفق خواهد بود. اگرcreate=Trueرا ارسال کنید و ویژگی وجود نداشته باشد، patch هنگام فراخوانی تابع patchشده، آن ویژگی را برای شما ایجاد میکند و پس از پایان اجرای تابع patchشده دوباره آن را حذف میکند. این برای نوشتن آزمونهایی برای ویژگیهایی که کد تولیدی شما در رانتایم ایجاد میکند مفید است. این گزینه بهطور پیشفرض غیرفعال است، زیرا میتواند خطرناک باشد. با فعال بودن آن میتوانید آزمونهای موفقی برای APIهایی بنویسید که واقعاً وجود ندارند!توجه
تغییر یافته در نسخهی 3.5: اگر در حال وصله کردن توکارها در یک ماژول هستید، نیازی به ارسال
create=Trueنیست؛ این آرگومان بهصورت پیشفرض اضافه میشود.میتوان از Patch بهعنوان دکوراتور کلاس
TestCaseاستفاده کرد. این کار با دکوراتور کردن هر متد آزمون در کلاس انجام میشود. وقتی متدهای آزمون شما یک مجموعهی مشترک از وصلهینگها (patchings) دارند، این کار کد تکراری را کاهش میدهد.patch()با جستوجوی نام متدهایی که باpatch.TEST_PREFIXشروع میشوند، آزمونها را پیدا میکند. بهطور پیشفرض این مقدار'test'است، که با روشی کهunittestآزمونها را پیدا میکند مطابقت دارد. شما میتوانید با تنظیمpatch.TEST_PREFIXیک پیشوند جایگزین مشخص کنید.میتوان از Patch بهعنوان یک مدیر زمینه، با دستور with استفاده کرد. در اینجا، وصلهکردن بر بلوک دارای تورفتگی پس از دستور with اعمال میشود. اگر از "as" استفاده کنید، شیء وصلهشده به نامی که پس از "as" میآید مقید میشود؛ اگر
patch()یک شیء ماک برای شما ایجاد کند، بسیار مفید است.patch()آرگومانهای کلیدواژهای دلخواه را میپذیرد. این آرگومانها اگر شیء وصلهشده ناهمگام باشد، بهAsyncMock، در غیر این صورت بهMagicMockیا در صورت مشخص بودن به new_callable ارسال میشوند.patch.dict(...)،patch.multiple(...)وpatch.object(...)برای موارد استفاده جایگزین در دسترس هستند.
patch() بهعنوان دکوراتور تابع، ماک را برای شما ایجاد میکند و آن را به تابع دکورشده میفرستد:
>>> @patch('__main__.SomeClass')
... def function(normal_argument, mock_class):
... print(mock_class is SomeClass)
...
>>> function(None)
True
وصله کردن یک کلاس، آن کلاس را با یک نمونه از MagicMock جایگزین میکند. اگر در کد تحت آزمون از این کلاس نمونهسازی شود، این return_value ماک است که استفاده خواهد شد.
اگر کلاس چندین بار نمونهسازی شود، میتوانید از side_effect استفاده کنید تا هر بار یک ماک جدید برگردانده شود. بهعنوان جایگزین، میتوانید return_value را روی هر مقداری که میخواهید تنظیم کنید.
برای پیکربندی مقادیر بازگشتی متدهای نمونههایی از کلاس وصلهشده، باید این کار را روی return_value انجام دهید. برای مثال:
>>> class Class:
... def method(self):
... pass
...
>>> with patch('__main__.Class') as MockClass:
... instance = MockClass.return_value
... instance.method.return_value = 'foo'
... assert Class() is instance
... assert Class().method() == 'foo'
...
اگر از spec یا spec_set استفاده کنید و patch() در حال جایگزینی یک class باشد، مقدار بازگشتی ماک ایجادشده همان spec را خواهد داشت.
>>> Original = Class
>>> patcher = patch('__main__.Class', spec=True)
>>> MockClass = patcher.start()
>>> instance = MockClass()
>>> assert isinstance(instance, Original)
>>> patcher.stop()
آرگومان new_callable زمانی مفید است که بخواهید بهجای کلاس پیشفرض MagicMock، از یک کلاس جایگزین برای ماک ایجادشده استفاده کنید. برای مثال، اگر بخواهید از NonCallableMock استفاده شود:
>>> thing = object()
>>> with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing:
... assert thing is mock_thing
... thing()
...
Traceback (most recent call last):
...
TypeError: 'NonCallableMock' object is not callable
یک مورد استفاده دیگر ممکن است جایگزینی یک شیء با یک نمونه io.StringIO باشد:
>>> from io import StringIO
>>> def foo():
... print('Something')
...
>>> @patch('sys.stdout', new_callable=StringIO)
... def test(mock_stdout):
... foo()
... assert mock_stdout.getvalue() == 'Something\n'
...
>>> test()
هنگامی که patch() برای شما یک ماک ایجاد میکند، معمول است که نخستین کاری که باید انجام دهید، پیکربندی ماک باشد. بخشی از این پیکربندی را میتوان در فراخوانی patch انجام داد. هر کلیدواژه دلخواهی که به فراخوانی منتقل کنید، برای تنظیم ویژگیهای ماک ایجادشده استفاده خواهد شد:
>>> patcher = patch('__main__.thing', first='one', second='two')
>>> mock_thing = patcher.start()
>>> mock_thing.first
'one'
>>> mock_thing.second
'two'
علاوه بر ویژگیهای ماک ایجادشده، میتوان ویژگیهایی مانند return_value و side_effect در ماکهای فرزند را نیز پیکربندی کرد. این موارد از نظر سینتکسی برای ارسال مستقیم بهعنوان آرگومانهای کلیدواژهای معتبر نیستند، اما همچنان میتوان یک دیکشنری با این موارد بهعنوان کلیدها را با استفاده از ** در یک فراخوانی patch() بسط داد:
>>> config = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> patcher = patch('__main__.thing', **config)
>>> mock_thing = patcher.start()
>>> mock_thing.method()
3
>>> mock_thing.other()
Traceback (most recent call last):
...
KeyError
بهطور پیشفرض، تلاش برای وصله کردن (patch) تابعی در یک ماژول (یا متدی یا ویژگیای در یک کلاس) که وجود ندارد، با AttributeError شکست میخورد:
>>> @patch('sys.non_existing_attribute', 42)
... def test():
... assert sys.non_existing_attribute == 42
...
>>> test()
Traceback (most recent call last):
...
AttributeError: <module 'sys' (built-in)> does not have the attribute 'non_existing_attribute'
اما افزودن create=True در فراخوانی patch() باعث میشود مثال قبلی همانطور که انتظار میرود کار کند:
>>> @patch('sys.non_existing_attribute', 42, create=True)
... def test(mock_stdout):
... assert sys.non_existing_attribute == 42
...
>>> test()
patch.object¶
- patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)¶
عضو نامگذاریشده (attribute) روی یک شیء (target) را با یک شیء ماک وصله کنید.
patch.object()میتواند بهعنوان دکوراتور، دکوراتور کلاس یا مدیر زمینه استفاده شود. آرگومانهای new، spec، create، spec_set، autospec و new_callable همان معنایی را دارند که برایpatch()دارند. مانندpatch()،patch.object()آرگومانهای کلیدواژهای دلخواهی را برای پیکربندی شیء ماکی که ایجاد میکند، میپذیرد.هنگامی که بهعنوان دکوراتور کلاس استفاده شود،
patch.object()برای انتخاب متدهایی که باید پوشش داده شوند،patch.TEST_PREFIXرا رعایت میکند.
میتوانید patch.object() را با سه آرگومان یا دو آرگومان فراخوانی کنید. حالت سهآرگومانی، شیء موردنظر برای وصلهشدن (patch)، نام ویژگی و شیءای که ویژگی با آن جایگزین میشود را دریافت میکند.
هنگام فراخوانی با قالب دو آرگومانی، شیء جایگزین را حذف میکنید و یک ماک برای شما ایجاد میشود و بهعنوان آرگومانی اضافی به تابع دکوریتشده ارسال میشود:
>>> @patch.object(SomeClass, 'class_method')
... def test(mock_method):
... SomeClass.class_method(3)
... mock_method.assert_called_with(3)
...
>>> test()
spec، create و سایر آرگومانهای patch.object() همان معنایی را دارند که برای patch() دارند.
patch.dict¶
- patch.dict(in_dict, values=(), clear=False, **kwargs)¶
یک دیکشنری، یا شیء شبهدیکشنری را وصله (patch) کنید و پس از آزمون، دیکشنری را به حالت اصلی خود بازگردانید؛ بهطوریکه دیکشنری بازیابیشده، نسخهای از دیکشنری همانگونه است که پیش از آزمون بوده است.
in_dict میتواند یک دیکشنری یا یک ظرف شبهنگاشت باشد. اگر یک نگاشت باشد، باید دستکم از دریافت، تنظیم و حذف آیتمها بههمراه تکرار روی کلیدها پشتیبانی کند.
in_dict همچنین میتواند رشتهای باشد که نام دیکشنری را مشخص میکند، که در این صورت با ایمپورت کردن آن واکشی میشود.
values میتواند یک دیکشنری از مقادیر برای تنظیم در دیکشنری باشد. values همچنین میتواند یک پیمایشپذیر از جفتهای
(key, value)باشد.اگر clear برابر با true باشد، دیکشنری پیش از تنظیم مقادیر جدید پاک میشود.
patch.dict()را همچنین میتوان با آرگومانهای کلیدواژهای دلخواه برای تنظیم مقادیر در دیکشنری فراخوانی کرد.تغییر یافته در نسخهی 3.8:
patch.dict()اکنون هنگامی که بهعنوان مدیر زمینه استفاده شود، دیکشنری وصلهشده را بازمیگرداند.
patch.dict() میتواند بهعنوان مدیر زمینه، دکوراتور یا دکوراتور کلاس استفاده شود:
>>> foo = {}
>>> @patch.dict(foo, {'newkey': 'newvalue'})
... def test():
... assert foo == {'newkey': 'newvalue'}
...
>>> test()
>>> assert foo == {}
هنگام استفاده بهعنوان دکوراتور کلاس، patch.dict() برای انتخاب متدهایی که باید پوشانده شوند، patch.TEST_PREFIX را (با مقدار پیشفرض 'test') رعایت میکند:
>>> import os
>>> import unittest
>>> from unittest.mock import patch
>>> @patch.dict('os.environ', {'newkey': 'newvalue'})
... class TestSample(unittest.TestCase):
... def test_sample(self):
... self.assertEqual(os.environ['newkey'], 'newvalue')
اگر میخواهید برای آزمون خود از پیشوند متفاوتی استفاده کنید، میتوانید با تنظیم patch.TEST_PREFIX، وصلهکنندهها (patchers) را از پیشوند متفاوت مطلع سازید. برای جزئیات بیشتر دربارهی نحوهی تغییر مقدار، TEST_PREFIX را ببینید.
patch.dict() میتواند برای افزودن اعضا به یک دیکشنری به کار رود، یا بهسادگی به یک آزمون اجازه دهد تا یک دیکشنری را تغییر دهد، و اطمینان حاصل کند که دیکشنری پس از پایان آزمون بازیابی میشود.
>>> foo = {}
>>> with patch.dict(foo, {'newkey': 'newvalue'}) as patched_foo:
... assert foo == {'newkey': 'newvalue'}
... assert patched_foo == {'newkey': 'newvalue'}
... # You can add, update or delete keys of foo (or patched_foo, it's the same dict)
... patched_foo['spam'] = 'eggs'
...
>>> assert foo == {}
>>> assert patched_foo == {}
>>> import os
>>> with patch.dict('os.environ', {'newkey': 'newvalue'}):
... print(os.environ['newkey'])
...
newvalue
>>> assert 'newkey' not in os.environ
میتوان از آرگومانهای کلیدواژهای در فراخوانی patch.dict() برای تنظیم مقادیر در دیکشنری استفاده کرد:
>>> mymodule = MagicMock()
>>> mymodule.function.return_value = 'fish'
>>> with patch.dict('sys.modules', mymodule=mymodule):
... import mymodule
... mymodule.function('some', 'args')
...
'fish'
میتوان از patch.dict() با اشیایی شبیه به دیکشنری استفاده کرد که در واقع دیکشنری نیستند. در حداقل حالت، آنها باید از دریافت، تنظیم و حذف آیتمها و نیز تکرار یا آزمون عضویت پشتیبانی کنند. این موضوع متناظر با متدهای جادویی __getitem__()، __setitem__()، __delitem__() و نیز __iter__() یا __contains__() است.
>>> class Container:
... def __init__(self):
... self.values = {}
... def __getitem__(self, name):
... return self.values[name]
... def __setitem__(self, name, value):
... self.values[name] = value
... def __delitem__(self, name):
... del self.values[name]
... def __iter__(self):
... return iter(self.values)
...
>>> thing = Container()
>>> thing['one'] = 1
>>> with patch.dict(thing, one=2, two=3):
... assert thing['one'] == 2
... assert thing['two'] == 3
...
>>> assert thing['one'] == 1
>>> assert list(thing) == ['one']
patch.multiple¶
- patch.multiple(target, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)¶
چندین وصله (patch) را در یک فراخوانی اعمال میکند. شیء مورد نظر برای وصله شدن (چه بهصورت شیء و چه بهصورت رشتهای برای واکشی شیء از طریق ایمپورت) و آرگومانهای کلیدواژهای برای وصلهها را دریافت میکند:
with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'): ...
اگر میخواهید
patch.multiple()ماکها را برای شما ایجاد کند، ازDEFAULTبهعنوان مقدار استفاده کنید. در این حالت، ماکهای ایجادشده بهصورت آرگومانهای کلیدواژهای به یک تابع دکوریتشده پاس داده میشوند، و هنگامی که ازpatch.multiple()بهعنوان یک مدیر زمینه استفاده شود، یک دیکشنری برگردانده میشود.patch.multiple()را میتوان بهعنوان دکوراتور، دکوراتور کلاس یا مدیر زمینه استفاده کرد. آرگومانهای spec، spec_set، create، autospec و new_callable همان معنایی را دارند که درpatch()دارند. این آرگومانها بر همهی وصلههایی که بهوسیلهیpatch.multiple()انجام میشوند، اعمال خواهند شد.هنگامی که بهعنوان دکوراتور کلاس استفاده شود،
patch.multiple()مقدارpatch.TEST_PREFIXرا برای انتخاب متدهایی که باید پوشانده شوند رعایت میکند.
اگر میخواهید patch.multiple() ماکها را برای شما ایجاد کند، آنگاه میتوانید از DEFAULT بهعنوان مقدار استفاده کنید. اگر از patch.multiple() بهعنوان آراینده استفاده کنید، ماکهای ایجادشده با کلیدواژه به تابع آراستهشده ارسال میشوند.
>>> thing = object()
>>> other = object()
>>> @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(thing, other):
... assert isinstance(thing, MagicMock)
... assert isinstance(other, MagicMock)
...
>>> test_function()
patch.multiple() میتواند با سایر دکوراتورهای patch تودرتو شود، اما آرگومانهای کلیدواژهای را پس از هر یک از آرگومانهای استانداردی که بهوسیله patch() ایجاد میشوند قرار دهید:
>>> @patch('sys.exit')
... @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(mock_exit, other, thing):
... assert 'other' in repr(other)
... assert 'thing' in repr(thing)
... assert 'exit' in repr(mock_exit)
...
>>> test_function()
اگر patch.multiple() بهعنوان یک مدیر زمینه استفاده شود، مقدار بازگشتی از مدیر زمینه یک دیکشنری است که در آن ماکهای ایجادشده با نام بهعنوان کلید قرار دارند:
>>> with patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) as values:
... assert 'other' in repr(values['other'])
... assert 'thing' in repr(values['thing'])
... assert values['thing'] is thing
... assert values['other'] is other
...
متدهای patch: start و stop¶
همهی وصلهکنندهها (patchers) متدهای start() و stop() را دارند. این متدها وصلهکردن در متدهای setUp یا هر جا که میخواهید چندین وصله را بدون تودرتو کردن دکوراتورها یا دستورهای with انجام دهید، سادهتر میکنند.
برای استفاده از آنها، patch()، patch.object() یا patch.dict() را بهصورت عادی فراخوانی کنید و یک ارجاع به شیء patcher برگرداندهشده نگه دارید. سپس میتوانید start() را برای اعمال وصله (patch) و stop() را برای لغو آن فراخوانی کنید.
اگر از patch() برای ایجاد یک ماک برای خودتان استفاده میکنید، آن با فراخوانی patcher.start برگردانده میشود.
>>> patcher = patch('package.module.ClassName')
>>> from package import module
>>> original = module.ClassName
>>> new_mock = patcher.start()
>>> assert module.ClassName is not original
>>> assert module.ClassName is new_mock
>>> patcher.stop()
>>> assert module.ClassName is original
>>> assert module.ClassName is not new_mock
یک مورد استفادهی رایج برای این ممکن است اعمال وصلههای متعدد در متد setUp یک TestCase باشد:
>>> class MyTest(unittest.TestCase):
... def setUp(self):
... self.patcher1 = patch('package.module.Class1')
... self.patcher2 = patch('package.module.Class2')
... self.MockClass1 = self.patcher1.start()
... self.MockClass2 = self.patcher2.start()
...
... def tearDown(self):
... self.patcher1.stop()
... self.patcher2.stop()
...
... def test_something(self):
... assert package.module.Class1 is self.MockClass1
... assert package.module.Class2 is self.MockClass2
...
>>> MyTest('test_something').run()
ملاحظه
اگر از این روش استفاده کنید، باید اطمینان حاصل کنید که وصلهکردن با فراخوانی stop «لغو» میشود. این کار ممکن است پیچیدهتر از آن باشد که تصور میکنید، زیرا اگر در setUp استثنایی پرتاب شود، tearDown فراخوانی نمیشود. unittest.TestCase.addCleanup() این کار را آسانتر میکند:
>>> class MyTest(unittest.TestCase):
... def setUp(self):
... patcher = patch('package.module.Class')
... self.MockClass = patcher.start()
... self.addCleanup(patcher.stop)
...
... def test_something(self):
... assert package.module.Class is self.MockClass
...
بهعنوان یک مزیت اضافی، دیگر نیازی به نگهداری ارجاع به شیء patcher نیست.
همچنین میتوانید با استفاده از patch.stopall() تمام وصلههایی را که آغاز شدهاند متوقف کنید.
- patch.stopall()¶
همهی وصلههای فعال را متوقف میکند. فقط وصلههایی را متوقف میکند که با
startشروع شدهاند.
وصله کردن توکارها¶
میتوانید هر یک از توکارها را در یک ماژول وصله کنید. مثال زیر تابع توکار ord() را وصله میکند:
>>> @patch('__main__.ord')
... def test(mock_ord):
... mock_ord.return_value = 101
... print(ord('c'))
...
>>> test()
101
TEST_PREFIX¶
میتوان از همهی وصلهکنندهها (patchers) بهعنوان دکوراتور کلاس استفاده کرد. وقتی به این شکل استفاده شوند، هر متد آزمون در کلاس را میپوشانند. وصلهکنندهها متدهایی را که با 'test' آغاز میشوند، بهعنوان متد آزمون میشناسند. این همان روشی است که unittest.TestLoader بهطور پیشفرض متدهای آزمون را پیدا میکند.
ممکن است بخواهید برای آزمونهایتان از پیشوند متفاوتی استفاده کنید. میتوانید با تنظیم patch.TEST_PREFIX، وصلهکنندهها را از پیشوند متفاوت مطلع کنید:
>>> patch.TEST_PREFIX = 'foo'
>>> value = 3
>>>
>>> @patch('__main__.value', 'not three')
... class Thing:
... def foo_one(self):
... print(value)
... def foo_two(self):
... print(value)
...
>>>
>>> Thing().foo_one()
not three
>>> Thing().foo_two()
not three
>>> value
3
تودرتو کردن دکوراتورهای Patch¶
اگر میخواهید چندین وصله (patch) اعمال کنید، میتوانید بهسادگی دکوراتورها را روی هم قرار دهید.
میتوانید با استفاده از این الگو، چندین دکوراتور patch را روی هم قرار دهید:
>>> @patch.object(SomeClass, 'class_method')
... @patch.object(SomeClass, 'static_method')
... def test(mock1, mock2):
... assert SomeClass.static_method is mock1
... assert SomeClass.class_method is mock2
... SomeClass.static_method('foo')
... SomeClass.class_method('bar')
... return mock1, mock2
...
>>> mock1, mock2 = test()
>>> mock1.assert_called_once_with('foo')
>>> mock2.assert_called_once_with('bar')
توجه داشته باشید که دکوراتورها از پایین به بالا اعمال میشوند. این روش استاندارد پایتون برای اعمال دکوراتورها است. ترتیب ماکهای ایجادشدهای که به تابع آزمون شما ارسال میشوند، با این ترتیب مطابقت دارد.
محل وصله (patch)¶
patch() با تغییر دادن (بهطور موقت) شیءای که یک نام به آن اشاره میکند به شیءای دیگر کار میکند. ممکن است نامهای زیادی به یک شیء واحد اشاره کنند، بنابراین برای اینکه وصله کردن کار کند، باید اطمینان حاصل کنید که نامی را وصله میکنید که سیستم تحت آزمون از آن استفاده میکند.
اصل اساسی این است که شما محلی را که یک شیء در آن جستجو میشود وصله (patch) میکنید، که لزوماً همان محلی نیست که در آن تعریفشده است. چند مثال به روشنشدن این موضوع کمک میکند.
فرض کنید پروژهای داریم که میخواهیم آن را با ساختار زیر آزمون کنیم:
a.py
-> Defines SomeClass
b.py
-> from a import SomeClass
-> some_function instantiates SomeClass
اکنون میخواهیم some_function را آزمون کنیم، اما میخواهیم با استفاده از patch()، SomeClass را ماک کنیم. مشکل این است که وقتی ماژول b را ایمپورت میکنیم (که باید این کار را انجام دهیم)، این ماژول SomeClass را از ماژول a ایمپورت میکند. اگر از patch() برای ماک کردن a.SomeClass استفاده کنیم، تأثیری بر آزمون ما نخواهد داشت؛ ماژول b از قبل ارجاعی به SomeClass واقعی دارد و به نظر میرسد وصله کردن ما بیاثر بوده است.
نکتهی کلیدی این است که SomeClass را در جایی که استفاده میشود (یا جایی که جستوجو میشود) با وصله (patch) جایگزین کنید. در این حالت، some_function در واقع SomeClass را در ماژول b جستوجو میکند، جایی که آن را ایمپورت کردهایم. وصله (patch) باید به این شکل باشد:
@patch('b.SomeClass')
با این حال، حالت جایگزینی را در نظر بگیرید که در آن بهجای from a import SomeClass، ماژول b import a را اجرا میکند و some_function از a.SomeClass استفاده میکند. هر دوی این روشهای ایمپورت رایج هستند. در این حالت، کلاسی که میخواهیم وصله (patch) کنیم، در ماژول جستجو میشود؛ بنابراین باید بهجای آن a.SomeClass را وصله (patch) کنیم:
@patch('a.SomeClass')
وصله کردن توصیفگرها و اشیاء پراکسی¶
هر دو patch و patch.object توصیفگرها را بهدرستی وصله و بازیابی میکنند: متدهای کلاس، متدهای ایستا و پراپرتیها. شما باید اینها را بهجای یک نمونه، روی کلاس وصله کنید. آنها همچنین با برخی اشیایی که دسترسی به ویژگیها را پراکسی میکنند، مانند شیء تنظیمات Django نیز کار میکنند.
پشتیبانی از MagicMock و متدهای جادویی¶
ماک کردن متدهای جادویی¶
Mock از ماک کردن متدهای پروتکل پایتون، که با نام "متدهای جادویی" نیز شناخته میشوند، پشتیبانی میکند. این کار به اشیاء ماک اجازه میدهد جایگزین ظرفها یا اشیاء دیگری شوند که پروتکلهای پایتون را پیادهسازی میکنند.
از آنجا که متدهای جادویی بهشکل متفاوتی نسبت به متدهای عادی جستجو میشوند [2]، این پشتیبانی بهطور جادویی پیادهسازی شده است. این بدان معناست که تنها متدهای جادویی خاصی پشتیبانی میشوند. فهرست پشتیبانیشوده شامل تقریباً همهی آنها میشود. اگر موردی وجود ندارد که به آن نیاز دارید، لطفاً به ما اطلاع دهید.
شما متدهای جادویی را با تنظیم متد مورد نظر خود به یک تابع یا یک نمونه ماک، ماک میکنید. اگر از یک تابع استفاده میکنید، آن تابع باید self را بهعنوان اولین آرگومان دریافت کند [3].
>>> def __str__(self):
... return 'fooble'
...
>>> mock = Mock()
>>> mock.__str__ = __str__
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__str__ = Mock()
>>> mock.__str__.return_value = 'fooble'
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__iter__ = Mock(return_value=iter([]))
>>> list(mock)
[]
یکی از موارد استفادهی این، ماک کردن اشیایی است که بهعنوان مدیر زمینه در یک دستور with استفاده میشوند:
>>> mock = Mock()
>>> mock.__enter__ = Mock(return_value='foo')
>>> mock.__exit__ = Mock(return_value=False)
>>> with mock as m:
... assert m == 'foo'
...
>>> mock.__enter__.assert_called_with()
>>> mock.__exit__.assert_called_with(None, None, None)
فراخوانیهای متدهای جادویی در method_calls ظاهر نمیشوند، اما در mock_calls ثبت میشوند.
توجه
اگر برای ایجاد یک ماک از آرگومان کلیدواژهای spec استفاده کنید، تلاش برای تنظیم یک متد جادویی که در spec وجود ندارد، باعث پرتاب استثنای AttributeError میشود.
فهرست کامل متدهای جادویی پشتیبانیشده عبارت است از:
__hash__،__sizeof__،__repr__و__str____dir__،__format__و__subclasses____round__،__floor__،__trunc__و__ceil__مقایسهها:
__lt__،__gt__،__le__،__ge__،__eq__و__ne__متدهای ظرف:
__getitem__،__setitem__،__delitem__،__contains__،__len__،__iter__،__reversed__و__missing__مدیر زمینه:
__enter__،__exit__،__aenter__و__aexit__متدهای عددی یکعملوندی:
__neg__،__pos__و__invert__متدهای عددی (شامل نسخههای سمت راست و درجا):
__add__،__sub__،__mul__،__matmul__،__truediv__،__floordiv__،__mod__،__divmod__،__lshift__،__rshift__،__and__،__xor__،__or__و__pow__متدهای تبدیل عددی:
__complex__،__int__،__float__و__index__متدهای توصیفگر:
__get__،__set__و__delete__پیکلکردن :
__reduce__،__reduce_ex__،__getinitargs__،__getnewargs__،__getstate__و__setstate__نمایش مسیر سامانه فایلبندی:
__fspath__متدهای تکرار ناهمگام:
__aiter__و__anext__
تغییر یافته در نسخهی 3.8: پشتیبانی از os.PathLike.__fspath__() افزوده شد.
تغییر یافته در نسخهی 3.8: پشتیبانی از __aenter__، __aexit__، __aiter__ و __anext__ اضافه شد.
متدهای زیر وجود دارند اما پشتیبانی نمیشوند، زیرا یا توسط ماک استفاده میشوند، یا نمیتوان آنها را بهصورت پویا تنظیم کرد، یا ممکن است مشکلاتی ایجاد کنند:
__getattr__،__setattr__،__init__و__new____prepare__,__instancecheck__,__subclasscheck__,__del__
ماک جادویی¶
۲ گونه از MagicMock وجود دارد: MagicMock و NonCallableMagicMock.
- class unittest.mock.MagicMock(*args, **kw)¶
MagicMockزیرکلاسی ازMockاست که پیادهسازیهای پیشفرضی برای بیشتر متدهای جادویی دارد. میتوانید بدون اینکه خودتان متدهای جادویی را پیکربندی کنید، ازMagicMockاستفاده کنید.پارامترهای سازنده همان معنای پارامترهای
Mockرا دارند.اگر از آرگومانهای spec یا spec_set استفاده کنید، فقط متدهای جادویی که در spec وجود دارند ایجاد میشوند.
- class unittest.mock.NonCallableMagicMock(*args, **kw)¶
نسخهای غیرفراخوانیپذیر از
MagicMock.پارامترهای سازنده همان معنایی را دارند که برای
MagicMockدارند، به استثنای return_value و side_effect که برای یک ماک غیرفراخوانیپذیر معنایی ندارند.
متدهای جادویی با اشیای MagicMock راهاندازی شدهاند، بنابراین میتوانید آنها را پیکربندی کنید و به روش معمول از آنها استفاده کنید:
>>> mock = MagicMock()
>>> mock[3] = 'fish'
>>> mock.__setitem__.assert_called_with(3, 'fish')
>>> mock.__getitem__.return_value = 'result'
>>> mock[2]
'result'
بهطور پیشفرض، بسیاری از متدهای پروتکل باید اشیایی از یک نوع مشخص را برگردانند. این متدها از پیش با یک مقدار بازگشتی پیشفرض پیکربندی شدهاند، بهطوری که اگر به مقدار بازگشتی علاقهای ندارید، میتوانید بدون نیاز به انجام هیچ کاری از آنها استفاده کنید. اگر بخواهید مقدار پیشفرض را تغییر دهید، همچنان میتوانید مقدار بازگشتی را بهصورت دستی تنظیم کنید.
متدها و پیشفرضهای آنها:
__lt__:NotImplemented__gt__:NotImplemented__le__:NotImplemented__ge__:NotImplemented__int__:1__contains__:False__len__:0__iter__:iter([])__exit__:False__aexit__:False__complex__:1j__float__:1.0__bool__:True__index__:1__hash__: هش پیشفرض برای ماک__str__: str پیشفرض برای ماک__sizeof__: اندازهی پیشفرض برای ماک
برای مثال:
>>> mock = MagicMock()
>>> int(mock)
1
>>> len(mock)
0
>>> list(mock)
[]
>>> object() in mock
False
دو متد برابری، __eq__() و __ne__()، خاص هستند. آنها مقایسه برابری پیشفرض بر اساس هویت را با استفاده از ویژگی side_effect انجام میدهند، مگر اینکه مقدار بازگشتی آنها را تغییر دهید تا چیز دیگری برگردانند:
>>> MagicMock() == 3
False
>>> MagicMock() != 3
True
>>> mock = MagicMock()
>>> mock.__eq__.return_value = True
>>> mock == 3
True
مقدار برگشتی __iter__() میتواند هر شیء پیمایشپذیری باشد و لازم نیست یک پیمایشگر باشد:
>>> mock = MagicMock()
>>> mock.__iter__.return_value = ['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']
اگر مقدار بازگشتی یک پیمایشگر باشد، آنگاه پیمایش روی آن برای یک بار، آن را مصرف میکند و پیمایشهای بعدی به یک فهرست خالی منجر میشوند:
>>> mock.__iter__.return_value = iter(['a', 'b', 'c'])
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
[]
MagicMock تمام متدهای جادویی پشتیبانیشده را بهصورت پیکربندیشده دارد، بهجز برخی از موارد مبهم و منسوخ. همچنان در صورت تمایل میتوانید آنها را پیکربندی کنید.
متدهای جادویی که پشتیبانی میشوند اما بهطور پیشفرض در MagicMock تنظیم نشدهاند:
__subclasses____dir____format____get__،__set__و__delete____reversed__و__missing____reduce__،__reduce_ex__،__getinitargs__،__getnewargs__،__getstate__و__setstate____getformat__
متدهای ویژه باید به جای نمونه، روی کلاس جستجو شوند. نسخههای مختلف پایتون در اعمال این قانون ناسازگار هستند. متدهای پروتکل پشتیبانیشده باید با تمام نسخههای پشتیبانیشده پایتون کار کنند.
تابع در اصل به کلاس متصل شده است، اما هر نمونه از Mock از سایر نمونهها جدا نگه داشته میشود.
کمککنندهها¶
نشانگر (sentinel)¶
- unittest.mock.sentinel¶
شیء
sentinelروشی مناسب برای فراهم کردن اشیای یکتا برای آزمونهایتان فراهم میکند.ویژگیها بهصورت درخواستی، هنگامی که با نام به آنها دسترسی پیدا میکنید، ایجاد میشوند. دسترسی به یک ویژگی یکسان همیشه یک شیء یکسان را برمیگرداند. اشیاء برگرداندهشده repr مناسبی دارند تا پیامهای شکست آزمون خوانا باشند.
گاهی هنگام آزمون، نیاز دارید بررسی کنید که یک شیء خاص بهعنوان آرگومان به متد دیگری ارسال میشود یا برگردانده میشود. ایجاد اشیای sentinel نامدار برای آزمون این موضوع میتواند رایج باشد. sentinel روش مناسبی برای ایجاد و آزمون هویت اشیایی مانند این فراهم میکند.
در این مثال، method را مانکیوصله (monkey patch) میکنیم تا sentinel.some_object را برگرداند:
>>> real = ProductionClass()
>>> real.method = Mock(name="method")
>>> real.method.return_value = sentinel.some_object
>>> result = real.method()
>>> assert result is sentinel.some_object
>>> result
sentinel.some_object
پیشفرض¶
- unittest.mock.DEFAULT¶
شیء
DEFAULTیک نشانگر از پیش ساختهشده (sentinel) است (در واقعsentinel.DEFAULT). توابعside_effectمیتوانند از آن برای نشان دادن اینکه باید از مقدار بازگشتی عادی استفاده شود، استفاده کنند.
فراخوانی¶
- unittest.mock.call(*args, **kwargs)¶
call()یک شیء کمکی برای ایجاد ادعاهای سادهتر و مقایسه باcall_args،call_args_list،mock_callsوmethod_callsاست. همچنین میتوانید ازcall()باassert_has_calls()استفاده کنید.>>> m = MagicMock(return_value=None) >>> m(1, 2, a='foo', b='bar') >>> m() >>> m.call_args_list == [call(1, 2, a='foo', b='bar'), call()] True
- call.call_list()¶
برای یک شیء فراخوانی که نشاندهنده چندین فراخوانی است،
call_list()فهرستی از همه فراخوانیهای میانی و همچنین فراخوانی نهایی را برمیگرداند.
call_list بهویژه برای نوشتن ادعاها در مورد «فراخوانیهای زنجیرهای» مفید است. یک فراخوانی زنجیرهای شامل چندین فراخوانی در یک خط کد است. این موضوع باعث ایجاد چندین ورودی در mock_calls روی یک ماک میشود. ساخت دستی دنبالهی فراخوانیها میتواند خستهکننده باشد.
call_list() میتواند دنباله فراخوانیها را از همان فراخوانی زنجیرهای بسازد:
>>> m = MagicMock()
>>> m(1).method(arg='foo').other('bar')(2.0)
<MagicMock name='mock().method().other()()' id='...'>
>>> kall = call(1).method(arg='foo').other('bar')(2.0)
>>> kall.call_list()
[call(1),
call().method(arg='foo'),
call().method().other('bar'),
call().method().other()(2.0)]
>>> m.mock_calls == kall.call_list()
True
یک شیء call بسته به چگونگی ساخت آن، یا یک تاپل از (آرگومانهای جایگاهی، آرگومانهای کلیدواژهای) است یا (نام، آرگومانهای جایگاهی، آرگومانهای کلیدواژهای). وقتی خودتان آنها را میسازید، این موضوع چندان جالب نیست، اما میتوان اشیاء call موجود در ویژگیهای Mock.call_args، Mock.call_args_list و Mock.mock_calls را دروننگری کرد تا به آرگومانهای منفردی که در بر دارند، دست یافت.
اشیای call در Mock.call_args و Mock.call_args_list تاپلهای دوتایی از (آرگومانهای جایگاهی، آرگومانهای کلیدواژهای) هستند، در حالی که اشیای call در Mock.mock_calls، به همراه آنهایی که خودتان میسازید، تاپلهای سهتایی از (نام، آرگومانهای جایگاهی، آرگومانهای کلیدواژهای) هستند.
میتوانید از «تاپلبودن» آنها برای استخراج تکتک آرگومانها جهت دروننگری و ادعاهای پیچیدهتر استفاده کنید. آرگومانهای جایگاهی یک تاپل هستند (اگر آرگومان جایگاهی وجود نداشته باشد، یک تاپل خالی هستند) و آرگومانهای کلیدواژهای یک دیکشنری هستند:
>>> m = MagicMock(return_value=None)
>>> m(1, 2, 3, arg='one', arg2='two')
>>> kall = m.call_args
>>> kall.args
(1, 2, 3)
>>> kall.kwargs
{'arg': 'one', 'arg2': 'two'}
>>> kall.args is kall[0]
True
>>> kall.kwargs is kall[1]
True
>>> m = MagicMock()
>>> m.foo(4, 5, 6, arg='two', arg2='three')
<MagicMock name='mock.foo()' id='...'>
>>> kall = m.mock_calls[0]
>>> name, args, kwargs = kall
>>> name
'foo'
>>> args
(4, 5, 6)
>>> kwargs
{'arg': 'two', 'arg2': 'three'}
>>> name is m.mock_calls[0][0]
True
create_autospec¶
- unittest.mock.create_autospec(spec, spec_set=False, instance=False, **kwargs)¶
یک شیء ماک را با استفاده از شیء دیگری بهعنوان مشخصه ایجاد کنید. ویژگیهای ماک از ویژگی متناظر در شیء spec بهعنوان مشخصه خود استفاده خواهند کرد.
آرگومانهای توابع یا متدهای ماکشده بررسی خواهند شد تا اطمینان حاصل شود که آنها با امضای صحیح فراخوانی میشوند.
اگر spec_set برابر
Trueباشد، تلاش برای تنظیم ویژگیهایی که در شیء spec وجود ندارند، باعث پرتابAttributeErrorمیشود.اگر از یک کلاس بهعنوان مشخصات استفاده شود، مقدار بازگشتی ماک (نمونهی کلاس) همان مشخصات را خواهد داشت. میتوانید با ارسال
instance=Trueاز یک کلاس بهعنوان مشخصات برای یک شیء نمونه استفاده کنید. ماک برگرداندهشده تنها در صورتی فراخوانیپذیر خواهد بود که نمونههای ماک فراخوانیپذیر باشند.create_autospec()همچنین آرگومانهای کلیدواژهای دلخواه را میپذیرد که به سازندهی ماک ایجادشده ارسال میشوند.
برای دیدن نمونههایی از چگونگی استفاده از مشخصهدهی خودکار (auto-speccing) با create_autospec() و آرگومان autospec برای patch()، مشخصهدهی خودکار (Autospeccing) را ببینید.
تغییر یافته در نسخهی 3.8: اگر هدف یک تابع ناهمگام باشد، create_autospec() اکنون یک AsyncMock برمیگرداند.
هر¶
- unittest.mock.ANY¶
گاهی ممکن است نیاز داشته باشید دربارهی برخی از آرگومانها در یک فراخوانی ماک ادعا کنید، اما یا برخی از آرگومانها برایتان مهم نباشد یا بخواهید آنها را بهصورت جداگانه از call_args استخراج کنید و ادعاهای پیچیدهتری دربارهی آنها مطرح کنید.
برای نادیده گرفتن برخی آرگومانها، میتوانید اشیایی را ارسال کنید که در مقایسه با همهچیز برابر هستند. در این صورت، فراخوانیهای assert_called_with() و assert_called_once_with() بدون توجه به اینکه چه چیزی ارسال شده باشد، موفق خواهند بود.
>>> mock = Mock(return_value=None)
>>> mock('foo', bar=object())
>>> mock.assert_called_once_with('foo', bar=ANY)
همچنین میتوان از ANY در مقایسه با فهرستهای فراخوانی مانند mock_calls استفاده کرد:
>>> m = MagicMock(return_value=None)
>>> m(1)
>>> m(1, 2)
>>> m(object())
>>> m.mock_calls == [call(1), call(1, 2), ANY]
True
ANY به مقایسه با اشیای فراخوانی محدود نمیشود و بنابراین میتواند در ادعاهای آزمون نیز استفاده شود:
class TestStringMethods(unittest.TestCase):
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', ANY])
FILTER_DIR¶
- unittest.mock.FILTER_DIR¶
FILTER_DIR متغیری در سطح ماژول است که نحوه پاسخ اشیای ماک به dir() را کنترل میکند. مقدار پیشفرض True است که از فیلتری که در زیر توضیح داده شده است استفاده میکند تا تنها اعضای مفید را نمایش دهد. اگر این فیلتر را نمیپسندید یا نیاز دارید آن را برای اهداف عیبیابی غیرفعال کنید، سپس mock.FILTER_DIR = False را تنظیم کنید.
با فعال بودن فیلتر، dir(some_mock) فقط ویژگیهای مفید را نشان میدهد و ویژگیهایی را که بهصورت پویا ایجاد شدهاند و بهطور معمول نمایش داده نمیشوند، شامل میشود. اگر ماک با یک spec (یا البته autospec) ایجاد شده باشد، تمام ویژگیهای شیء اصلی نمایش داده میشوند، حتی اگر هنوز به آنها دسترسی پیدا نشده باشد:
>>> dir(Mock())
['assert_any_call',
'assert_called',
'assert_called_once',
'assert_called_once_with',
'assert_called_with',
'assert_has_calls',
'assert_not_called',
'attach_mock',
...
>>> from urllib import request
>>> dir(Mock(spec=request))
['AbstractBasicAuthHandler',
'AbstractDigestAuthHandler',
'AbstractHTTPHandler',
'BaseHandler',
...
بسیاری از ویژگیهای نهچندان مفید (خصوصی برای Mock بهجای شیء ماکشده) که پیشوند زیرخط و زیرخط دوتایی دارند، از نتیجهی فراخوانی dir() روی یک Mock حذف شدهاند. اگر این رفتار را نمیپسندید، میتوانید با تنظیم کلید سطح ماژول FILTER_DIR آن را غیرفعال کنید:
>>> from unittest import mock
>>> mock.FILTER_DIR = False
>>> dir(mock.Mock())
['_NonCallableMock__get_return_value',
'_NonCallableMock__get_side_effect',
'_NonCallableMock__return_value_doc',
'_NonCallableMock__set_return_value',
'_NonCallableMock__set_side_effect',
'__call__',
'__class__',
...
بهعنوان جایگزین، میتوانید صرفاً از vars(my_mock) (اعضای نمونه) و dir(type(my_mock)) (اعضای نوع) برای دور زدن فیلترکردن، صرفنظر از FILTER_DIR، استفاده کنید.
mock_open¶
- unittest.mock.mock_open(mock=None, read_data='')¶
یک تابع کمکی برای ایجاد یک ماک جهت جایگزینی استفاده از
open(). این تابع برایopen()، چه مستقیماً فراخوانی شود و چه بهعنوان مدیر زمینه استفاده شود، کار میکند.آرگومان mock، شیء ماک برای پیکربندی است. اگر
None(پیشفرض) باشد، یکMagicMockبرای شما ایجاد میشود که API آن به متدها یا ویژگیهای موجود در دستههای پرونده استاندارد محدود است.read_data رشتهای است که متدهای
read()،readline()وreadlines()دسته پرونده باید آن را برگردانند. فراخوانیهای آن متدها داده را از read_data میگیرند تا زمانی که تمام شود. ماک این متدها نسبتاً ساده است: هر بار که ماک فراخوانی میشود، read_data به ابتدا بازمیگردد. اگر به کنترل بیشتری بر دادهای نیاز دارید که به کد آزمایششده میدهید، باید این ماک را خودتان سفارشی کنید. وقتی این کافی نباشد، یکی از بستههای سامانه فایلبندی درونحافظهای در PyPI میتواند یک سامانه فایلبندی واقعگرایانه برای آزمایش ارائه دهد.تغییر یافته در نسخهی 3.4: پشتیبانی از
readline()وreadlines()افزوده شد. ماکِread()تغییر کرد تا بهجای برگرداندن read_data در هر فراخوانی، آن را مصرف کند.تغییر یافته در نسخهی 3.5: اکنون read_data در هر فراخوانی به ماک بازنشانی میشود.
تغییر یافته در نسخهی 3.8:
__iter__()به پیادهسازی افزوده شد تا تکرار (مانند حلقههای for) read_data را بهدرستی مصرف کند.
استفاده از open() بهعنوان مدیر زمینه، روشی عالی برای اطمینان از بسته شدن صحیح دستههای پرونده است و در حال رایج شدن است:
with open('/some/path', 'w') as f:
f.write('something')
مشکل این است که حتی اگر فراخوانی open() را ماک کنید، این شیء برگرداندهشده است که بهعنوان مدیر زمینه استفاده میشود (و __enter__() و __exit__() فراخوانی میشوند).
ماک کردن مدیرهای زمینه با یک MagicMock بهقدری رایج و پردردسر است که یک تابع کمکی مفید واقع میشود.
>>> m = mock_open()
>>> with patch('__main__.open', m):
... with open('foo', 'w') as h:
... h.write('some stuff')
...
>>> m.mock_calls
[call('foo', 'w'),
call().__enter__(),
call().write('some stuff'),
call().__exit__(None, None, None)]
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')
و برای خواندن پروندهها:
>>> with patch('__main__.open', mock_open(read_data='bibble')) as m:
... with open('foo') as h:
... result = h.read()
...
>>> m.assert_called_once_with('foo')
>>> assert result == 'bibble'
مشخصهدهی خودکار (Autospeccing)¶
مشخصهدهی خودکار (Autospeccing) مبتنی بر ویژگی spec موجود در ماک است. این قابلیت، API ماکها را به API شیء اصلی محدود میکند، اما این محدودسازی بهصورت بازگشتی (و با پیادهسازی تنبلانه) انجام میشود، بهطوری که ویژگیهای ماکها تنها همان API ویژگیهای spec را دارند. علاوه بر این، توابع / متدهای ماکشده همان امضای فراخوانی نسخه اصلی را دارند، بنابراین اگر بهصورت نادرست فراخوانی شوند، یک TypeError پرتاب میکنند.
پیش از آنکه توضیح دهم که مشخصهسازی خودکار (auto-speccing) چگونه کار میکند، دلیل نیاز به آن در اینجا آمده است.
Mock یک شیء بسیار قدرتمند و انعطافپذیر است، اما دچار یک نقص است که در ماکسازی بهطور کلی وجود دارد. اگر بخشی از کد خود را بازنویسی کنید، نام اعضا را تغییر دهید و غیره، هر آزمونی برای کدی که هنوز از API قدیمی استفاده میکند، اما بهجای اشیاء واقعی از ماکها استفاده میکند، همچنان موفق میشود. این بدان معناست که حتی اگر کد شما خراب باشد، همهی آزمونهای شما میتوانند موفق شوند.
تغییر یافته در نسخهی 3.5: پیش از 3.5، آزمونهایی که در کلمه assert غلط املایی داشتند، در حالی که باید خطایی پرتاب میکردند، بیصدا قبول میشدند. هنوز هم میتوانید با ارسال unsafe=True به Mock این رفتار را داشته باشید.
توجه داشته باشید که این دلیل دیگری است که چرا به آزمونهای یکپارچگی و همچنین آزمون واحدها نیاز دارید. آزمون کردن همهچیز بهصورت جداگانه ممکن است کاملاً خوب باشد، اما اگر نحوهی «بههم متصل شدن» واحدهای خود را آزمون نکنید، هنوز جای زیادی برای باگهایی وجود دارد که آزمونها میتوانستند آنها را شناسایی کنند.
unittest.mock از قبل قابلیتی برای کمک به این موضوع ارائه میدهد که speccing نامیده میشود. اگر از یک کلاس یا نمونه بهعنوان spec برای یک ماک استفاده کنید، تنها میتوانید به ویژگیهایی از ماک دسترسی داشته باشید که در کلاس واقعی وجود دارند:
>>> from urllib import request
>>> mock = Mock(spec=request.Request)
>>> mock.assret_called_with # Intentional typo!
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'assret_called_with'
spec فقط به خود ماک اعمال میشود، بنابراین همچنان همان مشکل را با هر یک از متدهای ماک داریم:
>>> mock.header_items()
<mock.Mock object at 0x...>
>>> mock.header_items.assret_called_with() # Intentional typo!
این مشکل با specدهی خودکار (auto-speccing) حل میشود. شما میتوانید autospec=True را به patch() / patch.object() ارسال کنید یا از تابع create_autospec() برای ایجاد یک ماک با spec استفاده کنید. اگر از آرگومان autospec=True برای patch() استفاده کنید، شیءای که جایگزین میشود بهعنوان شیء spec استفاده خواهد شد. از آنجا که specدهی (speccing) بهصورت «تنبل» (lazily) انجام میشود (spec در حین دسترسی به ویژگیهای ماک ایجاد میشود)، میتوانید از آن با اشیاء بسیار پیچیده یا عمیقاً تودرتو (مانند ماژولهایی که ماژولهایی را ایمپورت میکنند که آنها ماژولهایی را ایمپورت میکنند) بدون افت عملکرد قابلتوجه استفاده کنید.
در اینجا مثالی از کاربرد آن آمده است:
>>> from urllib import request
>>> patcher = patch('__main__.request', autospec=True)
>>> mock_request = patcher.start()
>>> request is mock_request
True
>>> mock_request.Request
<MagicMock name='request.Request' spec='Request' id='...'>
میتوانید ببینید که request.Request دارای مشخصات است. request.Request در سازنده دو آرگومان میگیرد (که یکی از آنها self است). اگر سعی کنیم آن را بهصورت نادرست فراخوانی کنیم، این اتفاق میافتد:
>>> req = request.Request()
Traceback (most recent call last):
...
TypeError: <lambda>() takes at least 2 arguments (1 given)
spec همچنین به کلاسهای نمونهسازیشده نیز اعمال میشود (یعنی مقدار بازگشتی ماکهای دارای spec):
>>> req = request.Request('foo')
>>> req
<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>
اشیای Request فراخوانیپذیر نیستند، بنابراین مقدار بازگشتی نمونهسازی request.Request ماکشدهی ما یک ماک غیرفراخوانیپذیر است. با تنظیم مشخصات، هرگونه خطای تایپی در assertهای ما خطای صحیح را پرتاب خواهد کرد:
>>> req.add_header('spam', 'eggs')
<MagicMock name='request.Request().add_header()' id='...'>
>>> req.add_header.assret_called_with # Intentional typo!
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'assret_called_with'
>>> req.add_header.assert_called_with('spam', 'eggs')
در بسیاری از موارد، بهسادگی میتوانید autospec=True را به فراخوانیهای موجود patch() خود اضافه کنید و سپس در برابر خطاهای ناشی از اشتباهات تایپی و تغییرات API محافظت شوید.
علاوه بر استفاده از autospec از طریق patch()، create_autospec() نیز برای ایجاد ماکهای autospecشده بهصورت مستقیم وجود دارد:
>>> from urllib import request
>>> mock_request = create_autospec(request)
>>> mock_request.Request('foo', 'bar')
<NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>
البته این مورد بدون ملاحظات و محدودیتها نیست؛ به همین دلیل، رفتار پیشفرض نیست. برای اینکه بدانید چه ویژگیهایی در شیء spec در دسترس هستند، autospec باید spec را دروننگری (دسترسی به ویژگیها) کند. هنگامی که ویژگیهای ماک را پیمایش میکنید، پیمایش متناظری از شیء اصلی در پشت صحنه در حال انجام است. اگر هر یک از اشیاء دارای spec شما پراپرتیها یا توصیفگرهایی داشته باشند که بتوانند باعث اجرای کد شوند، ممکن است نتوانید از autospec استفاده کنید. از سوی دیگر، بسیار بهتر است اشیاء خود را بهگونهای طراحی کنید که دروننگری بیخطر باشد [4].
یک مشکل جدیتر این است که معمولاً ویژگیهای نمونه در متد __init__() ایجاد میشوند و اصلاً در کلاس وجود ندارند. autospec نمیتواند از هیچیک از ویژگیهایی که بهصورت پویا ایجاد میشوند آگاه باشد و API را به ویژگیهای قابلمشاهده محدود میکند.
>>> class Something:
... def __init__(self):
... self.a = 33
...
>>> with patch('__main__.Something', autospec=True):
... thing = Something()
... thing.a
...
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'a'
چند روش مختلف برای حل این مشکل وجود دارد. سادهترین، اما نه لزوماً کمآزارترین، روش این است که پس از ایجاد ماک، ویژگیهای مورد نیاز را بهسادگی روی آن تنظیم کنید. صرفاً به این دلیل که autospec به شما اجازه نمیدهد ویژگیهایی را که در مشخصه وجود ندارند دریافت کنید، مانع از تنظیم آنها نمیشود:
>>> with patch('__main__.Something', autospec=True):
... thing = Something()
... thing.a = 33
...
نسخهای سختگیرانهتر از هر دو spec و autospec وجود دارد که قطعاً شما را از تنظیم ویژگیهای ناموجود باز میدارد. این حالت زمانی مفید است که بخواهید کد شما نیز فقط ویژگیهای معتبر را تنظیم کند، اما بدیهی است که مانع این سناریوی خاص میشود:
>>> with patch('__main__.Something', autospec=True, spec_set=True):
... thing = Something()
... thing.a = 33
...
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'a'
احتمالاً بهترین راه برای حل این مسئله، افزودن ویژگیهای کلاس بهعنوان مقادیر پیشفرض برای اعضای نمونهای است که در __init__() مقداردهی اولیه میشوند. توجه داشته باشید که اگر فقط ویژگیهای پیشفرض را در __init__() تنظیم میکنید، فراهم کردن آنها از طریق ویژگیهای کلاس (که البته بین نمونهها مشترک هستند) سریعتر نیز خواهد بود. برای مثال
class Something:
a = 33
این موضوع، مسئله دیگری را پیش میآورد. نسبتاً رایج است که مقدار پیشفرض None برای اعضایی که بعداً شیءای از نوع دیگر خواهند بود، ارائه شود. None بهعنوان یک مشخصه بیفایده خواهد بود، زیرا به شما اجازه نمیدهد به هیچ ویژگی یا متدی از آن دسترسی داشته باشید. از آنجا که None هرگز بهعنوان یک مشخصه مفید نخواهد بود، و احتمالاً نشاندهنده عضوی است که معمولاً از نوع دیگری خواهد بود، autospec برای اعضایی که روی None تنظیم شدهاند از مشخصه استفاده نمیکند. اینها فقط ماکهای معمولی خواهند بود (البته - MagicMocks):
>>> class Something:
... member = None
...
>>> mock = create_autospec(Something)
>>> mock.member.foo.bar.baz()
<MagicMock name='mock.member.foo.bar.baz()' id='...'>
اگر تغییر کلاسهای محیط تولید برای افزودن مقادیر پیشفرض مورد پسند شما نیست، گزینههای بیشتری وجود دارد. یکی از این گزینهها این است که بهسادگی از یک نمونه بهعنوان مشخصه بهجای کلاس استفاده کنید. گزینه دیگر این است که یک کلاس فرعی از کلاس محیط تولید بسازید و مقادیر پیشفرض را به کلاس فرعی اضافه کنید، بدون آنکه کلاس محیط تولید تحت تأثیر قرار گیرد. هر دوی این موارد مستلزم آن است که از یک شیء جایگزین بهعنوان مشخصه استفاده کنید. خوشبختانه patch() از این حالت پشتیبانی میکند؛ میتوانید بهسادگی شیء جایگزین را بهعنوان آرگومان autospec ارسال کنید:
>>> class Something:
... def __init__(self):
... self.a = 33
...
>>> class SomethingForTest(Something):
... a = 33
...
>>> p = patch('__main__.Something', autospec=SomethingForTest)
>>> mock = p.start()
>>> mock.a
<NonCallableMagicMock name='Something.a' spec='int' id='...'>
این موضوع فقط برای کلاسها یا اشیای از قبل نمونهسازیشده صدق میکند. فراخوانی یک کلاس ماکشده برای ایجاد یک نمونه ماک، یک نمونه واقعی را ایجاد نمیکند. تنها جستوجوهای ویژگی — همراه با فراخوانیهای dir() — انجام میشوند.
مهر و موم کردن ماکها¶
- unittest.mock.seal(mock)¶
Seal ایجاد خودکار ماکها را هنگام دسترسی به ویژگیای از ماکِ در حال مهر و موم شدن یا هر یک از ویژگیهای آن که از قبل ماک هستند، بهصورت بازگشتی غیرفعال میکند.
اگر یک نمونه ماک دارای نام یا مشخصات به یک ویژگی انتساب داده شود، در زنجیره مهر و موم (sealing) در نظر گرفته نمیشود. این کار به شما اجازه میدهد از تثبیت بخشی از شیء ماک توسط seal جلوگیری کنید.
>>> mock = Mock() >>> mock.submock.attribute1 = 2 >>> mock.not_submock = mock.Mock(name="sample_name") >>> seal(mock) >>> mock.new_attribute # This will raise AttributeError. >>> mock.submock.attribute2 # This will raise AttributeError. >>> mock.not_submock.attribute2 # This won't raise.
اضافه شده در نسخهی 3.7.
ترتیب اولویت side_effect، return_value و wraps¶
ترتیب اولویت آنها به این صورت است:
wraps
اگر هر ۳ مورد تنظیم شده باشند، ماک مقدار را از side_effect برمیگرداند و return_value و شیء دربرگرفتهشده را بهکلی نادیده میگیرد. اگر هر ۲ مورد از آنها تنظیم شده باشند، موردی که اولویت بالاتری دارد مقدار را برمیگرداند. صرفنظر از اینکه کدامیک ابتدا تنظیم شده باشد، ترتیب اولویت بدون تغییر باقی میماند.
>>> from unittest.mock import Mock
>>> class Order:
... @staticmethod
... def get_value():
... return "third"
...
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.get_value.side_effect = ["first"]
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'first'
از آنجا که None مقدار پیشفرض side_effect است، اگر مقدار آن را دوباره به None انتساب دهید، ترتیب اولویت بین return_value و شیء پوششدادهشده بررسی خواهد شد و side_effect نادیده گرفته میشود.
>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'
اگر مقدار بازگرداندهشده توسط side_effect DEFAULT باشد، نادیده گرفته میشود و ترتیب اولویت برای به دست آوردن مقدار بازگشتی به جانشین منتقل میشود.
>>> from unittest.mock import DEFAULT
>>> order_mock.get_value.side_effect = [DEFAULT]
>>> order_mock.get_value()
'second'
هنگامی که Mock یک شیء را میپوشاند، مقدار پیشفرض return_value برابر با DEFAULT خواهد بود.
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.return_value
sentinel.DEFAULT
>>> order_mock.get_value.return_value
sentinel.DEFAULT
ترتیب اولویت، این مقدار را نادیده میگیرد و به آخرین جانشین، یعنی شیء پوششدادهشده، منتقل میشود.
از آنجا که فراخوانی واقعی به شیء پوشیدهشده انجام میشود، ایجاد یک نمونه از این ماک، نمونهی واقعی کلاس را برمیگرداند. آرگومانهای جایگاهی مورد نیاز شیء پوشیدهشده، در صورت وجود، باید ارسال شوند.
>>> order_mock_instance = order_mock()
>>> isinstance(order_mock_instance, Order)
True
>>> order_mock_instance.get_value()
'third'
>>> order_mock.get_value.return_value = DEFAULT
>>> order_mock.get_value()
'third'
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'second'
اما اگر None را به آن انتساب دهید، این نادیده گرفته نخواهد شد، زیرا یک انتساب صریح است. بنابراین، ترتیب اولویت به شیء پوششدادهشده منتقل نخواهد شد.
>>> order_mock.get_value.return_value = None
>>> order_mock.get_value() is None
True
حتی اگر هر سه را هنگام مقداردهی اولیه ماک یکجا تنظیم کنید، ترتیب اولویت یکسان باقی میماند:
>>> order_mock = Mock(spec=Order, wraps=Order,
... **{"get_value.side_effect": ["first"],
... "get_value.return_value": "second"}
... )
...
>>> order_mock.get_value()
'first'
>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'
>>> order_mock.get_value.return_value = DEFAULT
>>> order_mock.get_value()
'third'
If side_effect is exhausted, the order of precedence will not
cause a value to be obtained from the successors. Instead, StopIteration
exception is raised.
>>> order_mock = Mock(spec=Order, wraps=Order)
>>> order_mock.get_value.side_effect = ["first side effect value",
... "another side effect value"]
>>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value()
'first side effect value'
>>> order_mock.get_value()
'another side effect value'
>>> order_mock.get_value()
Traceback (most recent call last):
...
StopIteration