【问题标题】:Mock/Patch specific object method inside a class method类方法中的 Mock/Patch 特定对象方法
【发布时间】:2021-06-01 13:56:01
【问题描述】:

我知道标题很混乱,但为了清楚起见,我创建了一个我必须转置的示例:

假设我在class_file.py 中有这段代码

class CarDealership:
    def allow_car_out_of_the_dealership(self, car):
        logger.info(f"Driving out of the dealership")
        
        drivers_license = car.driver.wallet.get_drivers_license()

        if drivers_license:
            try:
                allow_car_removal(car, drivers_license)
            except:
                deny_car_removal(car, drivers_license)

我正在尝试测试调用方法deny_car_removal 的情况。 get_valid_license() 是特定于 driver.wallet 的方法,不会以任何方式、形状或形式在 class_file.py 中导入或引用。

问题是,如果get_valid_license 在实际情况下没有返回任何有效的东西,那么代码就会走完全不同的路。我正在尝试修补drivers_license,以便最终将它带到我需要的地方。

我猜test_class_file.py 应该是这样的:

class CarDealershipTests(BaseTestCase):
    @patch('class_file.CarDealership.deny_car_removal')
    def test_deny_car_removal(self):
        # something here
        self.assertTrue(mock_deny_car_removal.called)

我通过查看其他 StackOverflow 答案尝试了一些事情,但没有奏效:

  • 在测试设置中,在模拟的 car 内模拟完整的 driver 实例,如下所示:
    def setUp(self) -> None:
        self._create_car
    
    def _create_car(self):
        car = Car()
        car.driver = MagicMock()
  • 尝试使用@patch.object(class_file.CarDealership.allow_car_out_of_the_dealership, "car.driver.wallet.get_valid_license")
  • 尝试使用@patch.object(class_file.CarDealership.allow_car_out_of_the_dealership, "drivers_license")
  • 尝试使用@patch("class_file.CarDealership.allow_car_out_of_the_dealership.car.driver.wallet.get_valid_license")
  • 尝试使用@patch("class_file.CarDealership.allow_car_out_of_the_dealership.drivers_license")

【问题讨论】:

  • 这似乎是在使用标准的 Python 单元测试框架,关于您的实际逻辑,您应该明确了解您正在捕获的异常跨度>

标签: python unit-testing mocking pytest monkeypatching


【解决方案1】:

假设您有一个文件,其内容如下所示:

import logging

logger = logging.getLogger(__name__)

class CarRemovalFailure(BaseException):
    pass


class CarDealership:
    def allow_car_out_of_the_dealership(self, car):
        logger.info("Driving out of the dealership")
        
        drivers_license = car.driver.wallet.get_drivers_license()

        if drivers_license:
            try:
                allow_car_removal(car, drivers_license)
            except CarRemovalFailure:
                deny_car_removal(car, drivers_license)

为了调用deny_car_removal,我们需要allow_car_removal 抛出Exception。这可以通过使用Mock 对象的side_effect 属性轻松完成。

我没有在这个测试中加入任何设置/拆卸,而是说明side_effect 的工作原理以及它如何最终调用deny_car_removal

from unittest.mock import MagicMock, patch
from class_file import CarDealership, CarRemovalFailure


@patch("class_file.allow_car_removal")
@patch("class_file.deny_car_removal")
def test_deny_car(mock_deny, mock_allow):
    mock_allow.side_effect = CarRemovalFailure("Failed!")
    car = MagicMock()
    cd = CarDealership()

    cd.allow_car_out_of_the_dealership(car)

    car.driver.wallet.get_drivers_license.assert_called_once()
    mock_deny.assert_called_once()

运行时输出如下:

============================= test session starts ==============================
platform darwin -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: ****
collected 1 item                                                               

tests/test_car.py .                                                      [100%]

============================== 1 passed in 0.05s ===============================

在您的示例中,您可能不想在尝试测试逻辑时修补deny_car_removal,但由于我不知道那可能是什么,所以我最终修补了它。这样做的目的是为了说明实际上该函数确实被调用了。

【讨论】:

  • 嗨,金。感谢您非常彻底的回复,但我的主要疑问是如何模拟drivers_license。一旦该功能无法获得有效的功能,我的测试就会被中断,因为它是一个模拟。
  • 因为您将car 传递给函数,您可以像我一样创建一个对象car = MagickMock()。那么你只需要配置它,基本上就是car.driver.wallet.get_drivers_license.return_value = {"fizz": "buzz"}。那么当该对象被调用时,它将返回{"fizz": "buzz"}。希望这是有道理的。
  • 哦,我明白了!非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-18
  • 1970-01-01
  • 2015-11-23
  • 2019-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多