【问题标题】:Python mock - mocking class method that modifies class attributesPython mock - 修改类属性的模拟类方法
【发布时间】:2021-06-02 02:21:28
【问题描述】:

我目前有以下要测试的基本 Python 类:

class Example:

    def run_steps(self):
        self.steps = 0

        while self.steps < 4:
            self.step()
    
    def step(self):
        # some expensive API call
        print("wasting time...")
        time.sleep(1000)

        self.steps += 1

如您所见,step() 方法包含一个昂贵的 API 调用,因此我想使用另一个函数来模拟它,以避免昂贵的 API 调用但仍会递增 self.steps。我发现这样做是可能的(从here可以看出):

def mock_step(self):
    print("skip the wasting time")
    self.steps += 1

# This code works!
def test(mocker):
    example = Example()
    mocker.patch.object(Example, 'step', mock_step)

    example.run_steps()

我只是创建了一个名为 mock_step(self) 的函数来避免 API 调用,并使用新的 mock_step(self) 函数修补原来的慢速 step() 方法。

但是,这会导致一个新问题。由于mock_step(self)函数不是Mock对象,我不能调用它上面的任何Mock方法(比如assert_called()和call_count()):

def test(mocker):
    example = Example()
    mocker.patch.object(Example, 'step', mock_step)

    example.run_steps()

    # this line doesn't work
    assert mock_step.call_count == 4

为了解决这个问题,我尝试使用wraps 参数将mock_step 包装成一个Mock 对象:

def test(mocker):
    example = Example()

    # this doesn't work
    step = mocker.Mock(wraps=mock_step)
    mocker.patch.object(Example, 'step', step)

    example.run_steps()

    assert step.call_count == 4

然后我得到一个不同的错误说mock_step() missing 1 required positional argument: 'self'

所以从这个阶段开始,我不确定如何断言 step()run_steps() 中被调用了 4 次。

【问题讨论】:

    标签: python unit-testing mocking python-unittest.mock pytest-mock


    【解决方案1】:
    import unittest.mock as mock
    from functools import partial
    
    
    def fake_step(self):
        print("faked")
        self.steps += 1
    
    
    def test_api():
        api = Example()
        with mock.patch.object(api, attribute="step", new=partial(fake_step, self=api)):
            # we need to use `partial` to emulate that a real method has its `self` parameter bound at instantiation
            api.run_steps()
        assert api.steps == 4
    

    正确输出"faked" 4次。

    【讨论】:

      【解决方案2】:

      对此有多种解决方案,最简单的可能是使用具有副作用的标准模拟:

      def mock_step(self):
          print("skip the wasting time")
          self.steps += 1
      
      
      def test_step(mocker):
          example = Example()
          mocked = mocker.patch.object(Example, 'step')
          mocked.side_effect = lambda: mock_step(example)
          example.run_steps()
          assert mocked.call_count == 4
      

      side_effect 可以采用可调用对象,因此您既可以使用标准模拟方法,也可以使用修补方法。

      【讨论】:

      • 我用import mock 测试了您的解决方案,而不是注入mocker。但是您似乎修补了类的方法的定义,而不是实际的绑定到对象的方法,这会导致调用浪费时间的代码。
      • @Lenormju - 好吧,我按原样测试了代码,它对我有用(例如,不调用生产代码)。我使用了嘲笑者,因为问题使用了它,并且使用pytest 更方便。我还想允许使用标准mock 来允许call_countassert_called_xxx 等,而不是自己进行计数(当然,这也是一个解决方案)。
      • 你是对的,我错过了它出现在 OP 的代码中,我只需要pip install pytest-mock 就可以了。你的解决方案比我的好:)
      • 谢谢,这对我有用。我不知道mocker.patch 返回了一个修补对象的 Mock,所以我从来没有想过要做像mocked = mocker.patch.object(Example, 'step') 这样的事情。感谢您的帮助:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-12
      • 1970-01-01
      • 2023-03-18
      • 2018-09-21
      • 2020-02-17
      • 2020-06-17
      相关资源
      最近更新 更多