为了进行测试,模拟超类的__init__方法或整个超类

6

我想测试我写的一个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,我应该将整个 GenericClassGenericClass.__init__super 或者 GenericClass.aGenericClass.b 进行模拟吗?或者我的解决方案完全错误?请使用 mock 作为模拟库和 pytest 作为测试框架。要求解决方案与 Python2.6+ 兼容。谢谢!
1个回答

5
很难从一个人造实例中猜测出最佳方法。当只需使用所需的模拟并尽可能多地使用真实对象时,这是最好的选择。但这是一个权衡游戏,当创建真实对象很困难且依赖项非常复杂时,模拟可以成为非常强大的工具。此外,模拟会给您提供许多测试点。但是,这需要付出代价:有时,通过使用模拟,您可能会隐藏一些仅在集成测试中才能引发的错误。
对于您的情况,我建议最好只模拟a并设置fetch的返回值。您还可以测试a.initialize()的调用。
如果您的目标只是模拟a.initialize()(当然也包括a.fetch(),因为没有initialize()a.fetch()没有意义),最好的方法是只patcha对象。
无论如何,最后一个测试是关于修补超类。在您的情况下,这有点棘手,您必须同时修补__init__并注入ab属性。如果您担心GenericClass在init中做了什么,您必须直接修补__init__方法:修补类是无用的,因为它的引用已经在SpecificClass定义中解析(它由super而不是GenericClass.__init__(...)解析)。也许在实际代码中,您应该针对ab的只读属性进行斗争:同样,如果您可以使用真实对象并尽可能减少修补的方法/对象,那么这是最佳选择。我喜欢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()

谢谢你的回答!我知道这个例子有点短和抽象,但我尽量让问题尽可能通用。 - poros
1
然而,你的代码依赖于aself.a中存储的假设,这在这种情况下是正确的,但我不想依赖于来自外部库的类的内部逻辑。我们是否应该使用patch显式地模拟self.a呢? - poros

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接