【问题标题】:Python 2.7 mock a void method of a classPython 2.7 模拟类的 void 方法
【发布时间】:2018-11-18 11:25:28
【问题描述】:

我正在尝试使用unittests.mock 来模拟对象的 void 方法调用。

我的包裹如下

common
  baseupgradehandler.py

baseupgradehandler.py

class BaseUpgradeHandler(object):
    def __init__(self, upgrade_config, upgrade_state, system_config, pre_step, main_step, post_step):
        ...

    # Method call to be supressed
    def start(self, service_manifest, upgrade_bundle):
        # type: (service_version_pb2.ServiceManifest, str) -> ()
        ...

在我的测试代码中,我试图模拟对start() 的调用,如下所示,如documentation 中所述。

from workflow.upgradeworkflow import UpgradeWorkflow
from common.serviceregistry import ServiceRegistry
# The above imports are at the start of the test file
...
with patch('common.baseupgradehandler.BaseUpgradeHandler') as handler_mock:  # type: Mock
    handler_mock.return_value.start.return_value = ''                    
    wf = UpgradeWorkflow(ServiceRegistry(self.service_bundle, config, sys_config, state),
                         config,
                         state,
                         sys_config)

BaseUpgradeHandler 对象由ServiceRegistryget_upgrade_handler() 方法返回。当我在测试中执行上述代码时,我看到BaseUpgradeHandler.start() 仍在被调用。

有人可以告诉我如何模拟对start() 的调用,以便不调用该方法吗?

编辑

如果我像下面这样更改我的补丁代码,它会按预期工作,BaseUpgradeHandler 会被嘲笑,start 不会被调用。

with patch('common.baseupgradehandler.BaseUpgradeHandler') as handler_mock:  # type: Mock
    handler_mock.return_value.start.return_value = ''
    with patch('common.serviceregistry.ServiceRegistry') as serviceregistry_mock:  # type: Mock
        serviceregistry_mock.return_value.get_upgrade_handler.return_value = handler_mock
        wf = UpgradeWorkflow(ServiceRegistry(self.service_bundle, config, sys_config, state), config, state, sys_config)
        wf.start()

谁能解释我为什么还要修补ServiceRegistry

【问题讨论】:

  • patch 用作上下文管理器仅修补 with 块内的方法。你可能在外面叫它。如果您想为整个测试方法修补该方法,请使用 patch 作为装饰器。另外:如果你想patch('mcommon.baseupgradehandler.BaseUpgradeHandler.start') as method: method.return_value = '',你可以只模拟方法
  • @Bakuriu - 不,我没有在外面打电话。我已经编辑了这个问题。我不想修补整个测试方法。所以我使用了with。如果我遵循您的建议并制作patch('mcommon.baseupgradehandler.BaseUpgradeHandler.start') as method: method.return_value = '',那么这将按预期工作。我有多种方法可以模拟BaseUpgradeHandler。那么我必须按照您的建议模拟每种方法还是有其他方法?
  • 我无法重现您的问题。您如何检查是否调用了真正的方法?我们需要一个重现问题的完整示例,以了解为什么补丁在您的情况下不起作用。也许您正在通过修补之前获取的引用访问该类。如果你把patch作为装饰器,这个行为还会发生吗?
  • @Bakuriu - 我已经编辑了我的问题。它是更大代码库的一部分。我将尝试有一个完整的玩具示例。但是,如果上述编辑提供任何线索,请告诉我。
  • 只是提示:您来自庞大的代码库并不重要。首先“注释掉”该测试未使用的所有代码。然后注释掉该单个测试未直接使用的所有类/函数/方法。然后更改查找文件/数据库的函数的实现以仅返回固定结果。现在不管你来自哪里,你应该有非常少的类,几乎没有实现。看看问题是否仍然存在。如果没有尝试添加更多代码并重复。大多数情况下,通过这个过程,您会找到答案。

标签: python python-2.7 unit-testing


【解决方案1】:

您提供的代码不足以查看导致问题的部分。我们需要查看模块serviceregistry 才能确定,但​​我会做出有根据的猜测:

你有一个像这样的文件a.py(又名baseupgradehandler):

class A:
    def method(self):
        print("It's real!")

还有一个像这样的文件b.py(又名serviceregistry):

from a import A

class B:
    def get_A(self):
        return A()

在您的测试文件中,您可以这样做:

import unittest
from unittest.mock import patch
from b import B
from a import A

游戏结束!

B 模块现在已经获得了对原始A 类的引用。当,之后,你patch('a.A')a模块中的引用被改变了,但是patch却无法知道B有自己的参考原文A

您可以通过三种方式解决此问题:

  • 修补方法:这将修改现有类,以便自动修补对该类的所有引用
  • 也给b.A打补丁:

    with patch('a.A') as h_a, patch('b.A') as h_b:
        h_a.return_value.method.return_value = ''
        h_b.return_value.method.return_value = ''
    
  • 避免在修补之前导入模块(可能不可行或一个好主意):

    import unittest
    from unittest.mock import patch
    
    class MyTest(unittest.TestCase):
        def test_one(self):
            with patch('a.A') as h:
                h.return_value.method.return_value = ''
                from b import B
                B().get_A().method()
    

【讨论】:

    【解决方案2】:

    我使用 unittest.mocks 已经有一段时间了,有时我一直在重新发明轮子。我决定让 mockito 成为我项目的一部分,现在事情看起来好多了。任何类型的模拟验证都非常简单,如果可以的话,我绝对鼓励你将模拟作为库的一部分。这个库有一个很好的文档,到目前为止它比 unittest.mock 恕我直言更容易。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-24
      • 1970-01-01
      相关资源
      最近更新 更多