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 here 用 patch
装饰器之一装饰 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
上下文更具可读性。
我正在尝试理解 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 here 用 patch
装饰器之一装饰 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
上下文更具可读性。