使用自定义模拟类的Python unittest补丁

时间:2019-03-25 08:34:39

标签: python unit-testing

我正在测试一个相对简单的代码,该代码使用两个需要磁盘访问的相对复杂的对象。复杂对象来自库。我想模拟这些库对象,并断言它们的某些成员函数是根据输入的特定值来调用的。

我使用的调用依赖于库对象的某些内部状态。我知道我可以单独模拟复杂对象上的每个函数调用,并在每个对象之前设置return_value。但是,这似乎比需要的更加混乱和痛苦。

似乎您应该能够提供一个自定义类,并用Mock对象包装它,以跟踪对成员函数的所有调用。我花了几个小时试图弄清official documentation,发现的最接近的是wraps参数。不幸的是,这似乎并没有用包裹的模拟代替所有成员,而是仅使用模拟对象的成员变量,仅跟踪对__init__的调用。

例如在 module_a.py:

# Assuming some library class that acts something like this:
class A:
  def __init__(self, filename):
    self.filename = filename
  def get(self):
    # ... Some complicated code, involving state and disk access ...
    return something

def some_fnc(filename):
  a = A(filename)
  a.get()
  a.get()
  a.get()
  a.get()

test_module_a.py:

import unittest
import unittest.mock

import module_a

class MockA:
  def __init__(self, *args, **kwargs):
    self.i = 0
    pass
  def get(self):
    # Fake version that returns dummy values,
    # but more complicated than simply returning some value.
    # Easy to reason about. If this gets complicated then you have problems.
    print(self.i, 'is gotten')
    if self.i > 3:
      return 5
    self.i += 1
    return 1

class SomeFncTestCase(unittest.TestCase):
  @unittest.mock.patch('module_a.A', wraps=MockA)
  def test_it(self, m):
    module_a.some_fnc('in_file')
    m.assert_called_with('in_file')
    m.get.assert_called()

MockA.get被调用,第一个断言通过,但是第二个断言失败。

我缺少一些功能吗?可以做我想做的吗?我的建议有什么特别糟糕的理由吗?

1 个答案:

答案 0 :(得分:0)

您可以使用patch版的Mock对象,通过在被调用方尝试实例化module_a.A时将其设置为返回值,来返回包装模拟类的MagicMock实例。

class SomeFncTestCase(unittest.TestCase):
  @unittest.mock.patch('module_a.A')
  def test_it(self, m):
    mock_a = MagicMock(wraps=MockA())
    m.return_value = mock_a

    module_a.some_fnc('in_file')

    m.assert_called_with('in_file')
    mock_a.get.assert_called()

本质上模拟m的Mock A.__call__mock_a类的模拟实例A之间存在区别。包装MockA的实例)。