在Python中模拟对象的好方法?

时间:2015-02-09 22:44:53

标签: python unit-testing mocking

我试图了解Python Mock以更好地对我的代码进行单元测试。我过去没有进行过多次单元测试,但我想强调它向前发展。使用mock.patch('某些')作为模拟""对于我的代码正在使用的模拟对象,语法似乎非常方便。这对于模仿数据库或API调用特别方便。

但是,我注意到我写的测试数量增加了,我测试中的重复次数也增加了。如果我在我的类(MyClass下面)中使用了需要模拟的多个类,我需要将它们模拟为多个测试,即使它们不是直接用于特定测试。例如:

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调用,在测试过程中我实际上并不想这样做。但是由于我的课程正在使用每个,我需要为所有测试模拟所有这些。有更好的方法吗?

编辑:修复我的代码以反映我使用Mamba进行单元测试。我为最初没有提到这件事而道歉。

1 个答案:

答案 0 :(得分:1)

如果patch documentation

开头,则不仅仅是这种情况
  

patch()充当函数装饰器,类装饰器......

使用patch作为装饰器是提高可读性和简单性的最佳方法之一。你的案子成了

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")

此外,如果要为所有测试用例制作相同的补丁,可以装饰类而不是单个方法。 As documented here由一个patch装饰器装饰一个类,就像修补所有以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上下文更具可读性。