简体   繁体   English

python - 模拟 - class 方法模拟但未报告为被调用

[英]python - mock - class method mocked but not reported as being called

learning python mocks here.在这里学习 python 模拟。 I need some helps to understand how the patch work when mocking a class.我需要一些帮助来了解补丁在 mocking 和 class 时是如何工作的。

In the code below, I mocked a class. the function under tests receives the mock and calls a function on it.在下面的代码中,我模拟了一个 class。被测试的 function 接收模拟并在其上调用 function。 In my assertions, the class is successfully called, but the function is reported as not being called.在我的断言中,class 被成功调用,但 function 被报告为未被调用。

I added a debug print to view the content in the function under tests and it is reported as called.我添加了调试打印以查看测试中的 function 中的内容,并报告为已调用。

My expectation is the assertion assert facadeMock.install.called should be true.我的期望是断言assert facadeMock.install.called应该是真的。 Why is it not reported as called and how do I achieve this?为什么它没有被报告为已调用,我该如何实现?

Thank you.谢谢你。

install/__init__.py安装/__init__.py

from .facade import Facade

def main():
    f = Facade()
    f.install()
    print('jf-debug-> "f.install.called": {value}'.format(
        value=f.install.called))

test/install_tests.py测试/install_tests.py

import os
import sys
# allow import of package
sys.path.insert(0,
                os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from unittest.mock import patch

import install

@patch('install.Facade') # using autospec=True did not change the result
def test_main_with_links_should_call_facade_install_with_link_true(facadeMock):
    install.main()

    assert facadeMock.called
    assert facadeMock.install is install.Facade.install
    assert facadeMock.install.called   # <-------------------- Fails here!

output: output:

============================= test session starts ==============================
platform linux -- Python 3.10.6, pytest-7.2.0, pluggy-1.0.0
rootdir: /home/jfl/ubuntu-vim, configfile: pytest.ini
collected 1 item                                                               

test/install_tests.py jf-debug-> "f.install.called": True
F

=================================== FAILURES ===================================
________ test_main_with_links_should_call_facade_install_with_link_true ________

facadeMock = <MagicMock name='Facade' id='140679041900864'>

    @patch('install.Facade')
    def test_main_with_links_should_call_facade_install_with_link_true(facadeMock):
        install.main()
    
        assert facadeMock.called
    
        assert facadeMock.install is install.Facade.install
    
>       assert facadeMock.install.called
E       AssertionError: assert False
E        +  where False = <MagicMock name='Facade.install' id='140679042325216'>.called
E        +    where <MagicMock name='Facade.install' id='140679042325216'> = <MagicMock name='Facade' id='140679041900864'>.install

test/install_tests.py:21: AssertionError
=========================== short test summary info ============================
FAILED test/install_tests.py::test_main_with_links_should_call_facade_install_with_link_true - AssertionError: assert False
============================== 1 failed in 0.09s ===============================

[edit] [编辑]

Thank you to @chepner and @Daniil Fajnberg for their comments.感谢@chepner 和@Daniil Fajnberg 的评论。 I found the cause of the problem.我找到了问题的原因。

The problem can be reduced at: install/__init__.py receives an instance of Facade when calling Facade() in main().问题可以减少在: install/__init__.py在main() 中调用Facade() 时接收到一个Facade 的实例。 This instance is not the same as the one received in parameters of the test.此实例与测试参数中收到的实例不同。 They are different instances.它们是不同的实例。

to retrieve the instance received in main(), do:要检索在 main() 中接收到的实例,请执行以下操作:

    actualInstance = facadeMock.return_value
    assert actualInstance.install.called

And it works!它有效!

Thank you.谢谢你。 That really helps me understand the working of mocks in python.这确实有助于我理解 python 中模拟的工作原理。

[/edit] [/编辑]

I have found a method to solve your problem;我找到了解决您问题的方法; it is empirical but it works.它是经验性的,但它有效。

To pass your test I have modified it as you can see below:为了通过你的测试,我修改了它,如下所示:

@patch('install.Facade') # using autospec=True did not change the result
def test_main_with_links_should_call_facade_install_with_link_true(facadeMock):
    install.main()

    assert facadeMock.called
    assert facadeMock.install is install.Facade.install

    #assert facadeMock.install.called   # <-------------------- Fails here!
    install_called = False
    for call_elem in facadeMock.mock_calls:
        if call_elem[0] == "().install":
            install_called = True
            break
    assert install_called == True

The mock objects facadeMock and f模拟对象facadeMockf

facadeMock is a mock object created in the test code and it is used by the production code during your test to create the mock object f by the instruction: facadeMock是在测试代码中创建的模拟 object ,在测试期间生产代码使用它通过指令创建模拟 object f

f = Facade()

In the production code f is a mock object (that is an instance of the class Mock ) because it is created by the Mock object Facade that is exactly facadeMock .在生产代码中, f是一个 mock object(即 class Mock的一个实例),因为它是由 Mock object Facade创建的,它正是facadeMock

But f and facadeMock are 2 different instances of the class Mock .但是ffacadeMock是 class Mock的两个不同实例。

Below I show the id values of facadeMock , Facade and f :下面我展示了facadeMockFacadef的 id 值:

facadeMock = <MagicMock name='Facade' id='140449467990536'>
Facade = <MagicMock name='Facade' id='140449467990536'>
f = <MagicMock name='Facade()' id='140449465274608'>

The id for facadeMock , Facade but are different from the id of f . facadeMockFacade的 id 与f的 id 不同。

The attribute mock_calls属性 mock_calls

When your test code is executed the function install.main() execution causes the definition of the attribute mock_calls for the mock object facadeMock .当您的测试代码被执行时, function install.main()执行会导致模拟 object facadeMock的属性mock_calls的定义。

This attribute is a list of complex elements.该属性是复杂元素的列表。
If you check the first field (I mean the field in position 0) of each one of this element you can find the name of the methods of the mock that are called.如果您检查每个元素的第一个字段(我的意思是 position 0 中的字段),您可以找到被调用的模拟方法的名称。

In your case you have to found install and to do this you have to look for ().install .在您的情况下,您必须找到install并为此必须查找().install So my test checks all the element of mock_calls and only if ().install is found set the variable install_called=True .因此,我的测试检查了mock_calls的所有元素,并且仅当().install被发现时才设置变量install_called=True

I hope that this answer can help you.我希望这个答案可以帮助你。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM