Python 中模拟对象的好方法?

Good approach to mocking objects in Python?

我正在尝试理解 Python Mock 以更好地对我的代码进行单元测试。过去我没有做过太多的单元测试,但我想强调它向前发展。 "with mock.patch('something') as mock:" 语法对于模拟我的代码正在使用的对象来说似乎非常方便。这对于模拟数据库或 API 调用特别方便。

但是,我注意到随着我编写的测试数量的增加,我的测试中的重复也在增加。如果我在我的 class(下面的 MyClass)中使用了多个需要模拟的 classes,我需要模拟它们进行多次测试,即使它们没有直接用于特定测试。例如:

with context("my test"):
  with it('responds true'):
    with mock.patch('lib.mymodule.ClassA') as MockClassA:
      with mock.patch('lib.mymodule.ClassB') as MockClassB:
        with mock.patch('lib.mymodule.ClassC') as MockClassC:
          MockClassA.return_value = "bogus result"
          f = MyClass("host", "user", "password")
          self.assertEqual(f, "bogus result")

在这种情况下,MockClassA、B 和 C 可能会与数据库对话或进行 API 调用,我实际上并不想在测试期间这样做。但是由于我的 class 正在使用每个,我需要为所有测试模拟所有这些。有更好的方法吗?

编辑:修复了我的代码以反映我正在使用 Mamba 进行单元测试。我很抱歉最初没有提到这一点。

不只是 patch documentation

开头的情况

patch() acts as a function decorator, class decorator ....

使用补丁作为装饰器是提高可读性和简洁性的最佳方式之一。你的案例变成

def TestHostRecordCreation(self):
    @mock.patch('lib.mymodule.ClassC')
    @mock.patch('lib.mymodule.ClassB')
    @mock.patch('lib.mymodule.ClassA')
    def test_create_record(self, MockClassA, MockClassB, MockClassC):
        f = MyClass("host", "user", "password")
        self.assertEqual(f, "bogus result")

此外,如果您想为所有测试用例制作相同的补丁,您可以修饰 class 而不是单一方法。 As documented herepatch 装饰器之一装饰 class 就像修补所有以 patch.TEST_PREFIX 开头的方法一样。在您的情况下,我们使用 patch.TEST_PREFIX 的默认值,我们可以这样写:

@mock.patch('lib.mymodule.ClassC')
@mock.patch('lib.mymodule.ClassB')
@mock.patch('lib.mymodule.ClassA')
def TestHostRecordCreation(self):
    def test_A(self, MockClassA, MockClassB, MockClassC):
        f = MyClass("host", "user", "password")
        self.assertEqual(f, "bogus result")

    def test_B(self, MockClassA, MockClassB, MockClassC):
        f = MyClass("myhost", "myuser", "password")
        self.assertEqual(f, "other bogus result")

终于可以使用patch.multiple修补一组属性了。在那个特定的合成案例中,接缝非常强大,但在真实的单词案例中,它的使用非常罕见:

@mock.patch.multiple('lib.mymodule', ClassA=mock.DEFAULT, ClassB=mock.DEFAULT, ClassC=mock.DEFAULT)
def TestHostRecordCreation(self):
    def test_A(self, MockClassA, MockClassB, MockClassC):
        f = MyClass("host", "user", "password")
        self.assertEqual(f, "bogus result")

    def test_B(self, MockClassA, MockClassB, MockClassC):
        f = MyClass("myhost", "myuser", "password")
        self.assertEqual(f, "other bogus result")

如果您需要创建对大量测试有用的对象(每个测试单元框架都有类似的东西),请考虑使用 setUp()tearDown()。您可以使用 setUp()tearDown() 来启动和停止补丁上下文,但我的口味是装饰器和 with 上下文更具可读性。