模拟超类 __init__ 方法或整个超类进行测试

问题描述 投票:0回答:1

我想测试我写的一个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+。

提前致谢。

python testing mocking pytest
1个回答
5
投票

从综合示例中推断出最佳方法可能是什么有点困难。如果可能,只使用所需的模拟,因为尽可能多地使用真实对象是最好的方法。但这是一个权衡游戏,当创建真实对象很困难且依赖关系很深时,扭曲的模拟可能是一个非常强大的工具。此外,模拟可以让您更好地了解测试可以进行的地方。这是要付出代价的:有时通过使用模拟,您可以隐藏只能通过集成测试才能引发的错误。

恕我直言,对于您的情况,最好的方法是模拟

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()
© www.soinside.com 2019 - 2024. All rights reserved.