ยืนยันว่าฟังก์ชัน / วิธีการไม่ได้ถูกเรียกโดยใช้ Mock


131

ฉันใช้ไลบรารีจำลองเพื่อทดสอบแอปพลิเคชันของฉัน แต่ฉันต้องการยืนยันว่าไม่ได้เรียกใช้ฟังก์ชันบางอย่าง เอกสารจำลองพูดคุยเกี่ยวกับวิธีการเช่นmock.assert_called_withและmock.assert_called_once_withแต่ฉันไม่พบอะไรที่เหมือนmock.assert_not_calledหรือสิ่งที่เกี่ยวข้องกับการตรวจสอบการเยาะเย้ยถูกไม่ได้เรียกว่า

ฉันสามารถไปกับสิ่งต่อไปนี้แม้ว่ามันจะดูไม่เท่หรือไม่พีโธนิก:

def test_something:
    # some actions
    with patch('something') as my_var:
        try:
            # args are not important. func should never be called in this test
            my_var.assert_called_with(some, args)
        except AssertionError:
            pass  # this error being raised means it's ok
    # other stuff

มีความคิดอย่างไรที่จะทำสิ่งนี้ให้สำเร็จ?


ดังที่ @Ahmet ชี้ให้เห็นในคำตอบของเขาขณะนี้ assert_not_called ได้รับการสนับสนุนแล้วใน backport ( docs.python.org/3/library/… )
Martin

คำตอบ:


144

สิ่งนี้ควรใช้ได้กับกรณีของคุณ

assert not my_var.called, 'method should not have been called'

ตัวอย่าง;

>>> mock=Mock()
>>> mock.a()
<Mock name='mock.a()' id='4349129872'>
>>> assert not mock.b.called, 'b was called and should not have been'
>>> assert not mock.a.called, 'a was called and should not have been'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: a was called and should not have been

คำตอบนี้ต้องการ Django หรือไม่? ฉันได้รับข้อผิดพลาด:AttributeError: MockCallable instance has no attribute 'called'
Nathan Arthur

@NathanArthur อืมฉันไม่คิดอย่างนั้นหลังจากนั้นsudo easy_install -U mockและfrom mock import Mockบน MacOS ข้างต้นจะทำงานโดยไม่มีปัญหา ไม่เคยติดตั้ง Django :)
Joachim Isaksson

อืมมม นั่นเป็นเรื่องแปลก ฉันใช้ Python 2.7.1 และกำลังใช้ unittest และfrom mock import MockPython Mock 0.1.0 สำหรับการทดสอบของฉัน เสียงนั้นมีปัญหาหรือไม่?
Nathan Arthur

ฉันกำลังล้อเลียนคลาสที่เรียกได้จากโมดูลอื่นดังนั้นดูเหมือนว่าmodule_to_test.another_module.class = mock.Mock()คุณสามารถยืนยันได้ไหมว่าสิ่งนี้ไม่จำการโทรในกรณีทดสอบต่างๆ (อินสแตนซ์ unittest.TestCase) ฉันคิดว่าจำนวนการโทรจะไม่รีเซ็ตในกรณีนี้
0xc0de

67

แม้ว่าจะเป็นคำถามเก่า แต่ฉันต้องการเพิ่มว่าmockห้องสมุดปัจจุบัน(backport of unittest.mock) รองรับassert_not_calledวิธีการ

เพียงอัปเกรดของคุณ

pip install mock --upgrade


29

คุณสามารถตรวจสอบcalledแอตทริบิวต์ได้ แต่หากการยืนยันของคุณล้มเหลวสิ่งต่อไปที่คุณต้องการทราบคือบางอย่างเกี่ยวกับการโทรที่ไม่คาดคิดดังนั้นคุณอาจจัดเตรียมข้อมูลดังกล่าวให้แสดงตั้งแต่เริ่มต้น โดยใช้unittestคุณสามารถตรวจสอบเนื้อหาcall_args_listแทน:

self.assertItemsEqual(my_var.call_args_list, [])

เมื่อล้มเหลวจะมีข้อความดังนี้:

AssertionError: จำนวนองค์ประกอบไม่เท่ากัน:
อันดับแรกมี 0 วินาทีมี 1: โทร ('อาร์กิวเมนต์แรก', 4)

14

เมื่อคุณทดสอบโดยใช้คลาสที่สืบทอดunittest.TestCaseคุณสามารถใช้วิธีการเช่น:

  • assertTrue
  • assertFalse
  • assertEqual

และคล้ายกัน (ในเอกสาร pythonคุณพบส่วนที่เหลือ)

ในตัวอย่างของคุณเราสามารถยืนยันได้ว่าคุณสมบัติmock_method.calledเป็นเท็จหรือไม่ซึ่งหมายความว่าไม่มีการเรียกเมธอดนั้น

import unittest
from unittest import mock

import my_module

class A(unittest.TestCase):
    def setUp(self):
        self.message = "Method should not be called. Called {times} times!"

    @mock.patch("my_module.method_to_mock")
    def test(self, mock_method):
        my_module.method_to_mock()

        self.assertFalse(mock_method.called,
                         self.message.format(times=mock_method.call_count))


1

เมื่อพิจารณาจากคำตอบอื่น ๆ ไม่มีใครนอกจาก@ rob-kennedyได้พูดคุยเกี่ยวกับไฟล์call_args_list.

เป็นเครื่องมือที่มีประสิทธิภาพที่คุณสามารถใช้ในทางตรงกันข้าม MagicMock.assert_called_with()

call_args_listคือรายการcallวัตถุ แต่ละcallออบเจ็กต์แสดงถึงการโทรที่สร้างขึ้นโดยสามารถเรียกได้

>>> from unittest.mock import MagicMock
>>> m = MagicMock()
>>> m.call_args_list
[]
>>> m(42)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42)]
>>> m(42, 30)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42), call(42, 30)]

การใช้callออบเจ็กต์เป็นเรื่องง่ายเนื่องจากคุณสามารถเปรียบเทียบกับทูเปิลของความยาว 2 โดยที่องค์ประกอบแรกคือทูเพิลที่มีอาร์กิวเมนต์ตำแหน่งทั้งหมดของการเรียกที่เกี่ยวข้องในขณะที่องค์ประกอบที่สองคือพจนานุกรมของอาร์กิวเมนต์คำหลัก

>>> ((42,),) in m.call_args_list
True
>>> m(42, foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((42,), {'foo': 'bar'}) in m.call_args_list
True
>>> m(foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((), {'foo': 'bar'}) in m.call_args_list
True

ดังนั้นวิธีแก้ปัญหาเฉพาะของ OP คือ

def test_something():
    with patch('something') as my_var:
        assert ((some, args),) not in my_var.call_args_list

โปรดทราบว่าวิธีนี้แทนที่จะตรวจสอบว่ามีการเรียกชื่อเรียกที่เยาะเย้ยผ่านMagicMock.calledคุณสามารถตรวจสอบได้ว่ามีการเรียกด้วยอาร์กิวเมนต์เฉพาะหรือไม่

มีประโยชน์ สมมติว่าคุณต้องการทดสอบฟังก์ชันที่รับรายการและเรียกใช้ฟังก์ชันอื่นcompute()สำหรับค่าแต่ละค่าของรายการก็ต่อเมื่อฟังก์ชันนั้นเป็นไปตามเงื่อนไขที่กำหนดเท่านั้น

ตอนนี้คุณสามารถเยาะเย้ยcomputeและทดสอบว่ามีการเรียกค่านิยมบางอย่าง แต่ไม่ถูกเรียกใช้กับผู้อื่น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.