【问题标题】:Mock superclass __init__ method or superclass as a whole for testing模拟超类 __init__ 方法或超类作为一个整体进行测试
【发布时间】:2015-01-12 17:17:34
【问题描述】:

我想测试我写的一个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__superGenericClass.aGenericClass.b进行模拟吗?还是我解决问题的方法完全错误?

请使用mock 作为模拟库,pytest 作为测试框架。解决方案需要兼容Python2.6+。

提前致谢。

【问题讨论】:

    标签: python testing mocking pytest


    【解决方案1】:

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

    恕我直言,对于您的情况,最好的方法是模拟 a 并设置 fetch 的返回值。也可以测试a.initialize()调用

    如果您的目标只是模拟a.initialize()(当然a.fetch(),我猜如果没有initialize(),那将毫无意义);最好的就是 patch a 对象。

    无论如何,最后一个测试是关于修补超类。在您的情况下,这有点棘手,您必须同时修补 __init__ 并注入 ab 属性。如果您担心 GenericClass 在 init 中所做的事情,您必须直接对其进行修补 __init__ 方法:修补该类是无用的,因为它的引用已经在 SpecificClass 定义中(它由 super 解决,而不是 @987654336 @)。也许在实际代码中,您应该与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()
    

    【讨论】:

    • 感谢您的回答!我知道这个例子有点简短和抽象,但我尽量让这个问题尽可能笼统。
    • 但是,您的代码依赖于 a 按原样存储在 self.a 中的假设,在这种情况下是正确的,但我宁愿不依赖于类的内部逻辑来自外部库。我们应该使用patch 来显式模拟self.a 吗?
    猜你喜欢
    • 1970-01-01
    • 2021-03-31
    • 2015-11-27
    • 2018-12-23
    • 2013-07-24
    • 2018-11-10
    • 1970-01-01
    • 1970-01-01
    • 2012-02-03
    相关资源
    最近更新 更多