我想测试我写的一个Python类,如下所示:
from external_library import GenericClass
class SpecificClass(GenericClass):
def __init__(self, a, b, c):
super(SpecificClass, self).__init__(a, b)
self.c = c
def specific_method(self, d):
self.initialize()
return d + self.c + self.b + self.a.fetch()
GenericClass
由外部库定义 (external_library
):
class GenericClass(object):
def __init__(self, a, b):
self.a = a
self.b = b + "append"
def initialize(self):
# it might be an expensive operation
self.a.initialize()
def specific_method(self, d):
raise NotImplementedError
我应该如何测试
specific_method
?除了GenericClass.initialize
之外,我应该模拟整个GenericClass
,GenericClass.__init__
,super
还是GenericClass.a
和GenericClass.b
?或者我解决问题的方法完全错误?
请使用
mock
作为模拟库,使用pytest
作为测试框架。该解决方案需要兼容Python2.6+。
提前致谢。
从综合示例中推断出最佳方法可能是什么有点困难。如果可能,只使用所需的模拟,因为尽可能多地使用真实对象是最好的方法。但这是一个权衡游戏,当创建真实对象很困难且依赖关系很深时,扭曲的模拟可能是一个非常强大的工具。此外,模拟可以让您更好地了解测试可以进行的地方。这是要付出代价的:有时通过使用模拟,您可以隐藏只能通过集成测试才能引发的错误。
恕我直言,对于您的情况,最好的方法是模拟
a
并设置 fetch
的返回值。您还可以测试对 a.initialize()
的调用。
如果你的目标只是嘲笑
a.initialize()
(当然还有a.fetch()
,因为我认为没有initialize()
就没有意义);最好的事情就是 patch
a
对象。
无论如何,最后一个测试是关于修补超类的。在你的情况下,这有点棘手,你必须修补
__init__
并注入 a
,b
属性。如果你担心 GenericClass
在 init 中做了什么,你必须直接修补它 __init__
方法:修补类是没有用的,因为它的引用已经在 SpecificClass
定义中(它是由 super
解析的,而不是由 GenericClass.__init__(...)
解析的)
)。也许在真实的代码中,你应该反对 a
和 b
的只读属性:同样,如果你可以使用真实的对象并且尽可能少地修补方法/对象是最好的选择。我喜欢 mock
框架,但有时使用它并不是最好的选择。
还有一件事:我使用
patch.multiple
只是为了有一个更紧凑的示例,但通过两个 patch.object
修补该方法可以完成相同的工作。
import unittest
from mock import Mock, patch, DEFAULT, PropertyMock
from specific import SpecificClass
class A():
def initialize(self):
raise Exception("Too slow for tests!!!!")
def fetch(self):
return "INVALID"
class MyTestCase(unittest.TestCase):
def test_specific_method(self):
a = A()
with patch.multiple(a, initialize=DEFAULT, fetch=DEFAULT) as mocks:
mock_initialize, mock_fetch = mocks["initialize"], mocks["fetch"]
mock_fetch.return_value = "a"
sc = SpecificClass(a, "b", "c")
self.assertEqual("dcbappenda", sc.specific_method("d"))
mock_initialize.assert_called_with()
def test_sanity_check(self):
a = A()
sc = SpecificClass(a, "b", "c")
self.assertRaises(Exception, sc.specific_method, "d")
#You can patch it in your module if it is imported by from .. as ..
@patch("specific.GenericClass.__init__", autospec=True, return_value=None)
def test_super_class(self, mock_init):
sc = SpecificClass("a", "b", "c")
mock_init.assert_called_with(sc, "a", "b")
#Inject a and b
sc.a = Mock()
sc.b = "b"
#configure a
sc.a.fetch.return_value = "a"
self.assertEqual("dcba", sc.specific_method("d"))
sc.a.initialize.assert_called_with()