【发布时间】:2021-01-23 10:15:36
【问题描述】:
我想做什么
我有一个具有多重继承的类,我想为其准备单元测试。
我为什么要这样做
假设我编写了一些程序,其中有动物和植物。我有很多不同的动物(很多动物子类),植物也是如此。这就是为什么我需要动物和植物类型。动植物种类繁多,这就是为什么我在动植物界都有很多传承。不幸的是,我有一种既是植物又是动物的类型(例如,具有类似橡木属性的狮子 - 示例中的 A i B 类)。我想做的是将植物和动物的属性和方法混合到这一种类型(C类)中。我知道这听起来很荒谬,但这是我的确切目标,我预计会出现错误,因为它非常特别。这就是为什么我想在那里进行适当的测试,以免引入错误。
第一个想法
假设我的代码如下所示(我愿意接受 C.__init__ 改进建议):
inheritance.py
class A:
def __init__(self, p1):
self.p1 = p1
class B:
def __init__(self, p2):
self.p2 = p2
class C(A, B):
def __init__(self, p1, p2):
# I want to execute init methods of both A and B.
A.__init__(self=self, p1=p1)
B.__init__(self=self, p2=p2)
我对单元测试的第一个想法是这样的:
test_inheritance.py
import pytest
from mock import Mock, patch
from inheritance import C
class TestC:
def setup(self):
self.mock_c_object = Mock(spec=C)
# patching A.__init__ and B.__init__
self._patcher_a_init_ = patch("inheritance.A.__init__")
self.mock_a_init = self._patcher_a_init_.start()
self._patcher_b_init_ = patch("inheritance.B.__init__")
self.mock_b_init = self._patcher_b_init_.start()
def teardown(self):
self._patcher_a_init_.stop()
self._patcher_b_init_.stop()
@pytest.mark.parametrize("p1", [0, 1])
@pytest.mark.parametrize("p2", ["x", "y"])
def test_c_init(self, p1, p2):
C.__init__(self=self.mock_c_object, p1=p1, p2=p2)
self.mock_a_init.assert_called_once_with(self=self.mock_c_object, p1=p1)
self.mock_b_init.assert_called_once_with(self=self.mock_c_object, p2=p2)
有什么问题
当我尝试运行这个测试时,我遇到了这个回溯:
test_inheritance.py:19 (TestC.test_c_init[x-0])
self = <test_inheritance.TestC object at 0x042C0520>, p1 = 0, p2 = 'x'
@pytest.mark.parametrize("p1", [0, 1])
@pytest.mark.parametrize("p2", ["x", "y"])
def test_c_init(self, p1, p2):
> C.__init__(self=self.mock_c_object, p1=p1, p2=2)
test_inheritance.py:23:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
inheritance.py:15: in __init__
A.__init__(self=self, p1=p1)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_mock_self = <MagicMock name='__init__' id='70100424'>, args = ()
kwargs = {'p1': 0, 'self': <Mock spec='C' id='69801632'>}
def __call__(_mock_self, *args, **kwargs):
# can't use self in-case a function / method we are mocking uses self
# in the signature
> _mock_self._mock_check_sig(*args, **kwargs)
E TypeError: _mock_check_sig() got multiple values for argument 'self'
C:\Python38\lib\site-packages\mock\mock.py:1098: TypeError
我是如何解决这个问题的
我想为了解决模拟问题(多个自签名),我必须开始在C.__init__ 中使用super。
我尝试在 inheritance.py 中进行此类更新:
class C(A, B):
def __init__(self, p1, p2):
super(B, C).__init__(p1=p1)
super(A, C).__init__(p2=p2)
很遗憾,我的代码有问题:
test_inheritance.py:19 (TestC.test_c_init[x-0])
self = <test_inheritance.TestC object at 0x0414E268>, p1 = 0, p2 = 'x'
@pytest.mark.parametrize("p1", [0, 1])
@pytest.mark.parametrize("p2", ["x", "y"])
def test_c_init(self, p1, p2):
> C.__init__(self=self.mock_c_object, p1=p1, p2=2)
test_inheritance.py:23:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock spec='C' id='68478408'>, p1 = 0, p2 = 2
def __init__(self, p1, p2):
> super(B, C).__init__(p1=p1)
E TypeError: descriptor '__init__' of 'object' object needs an argument
inheritance.py:13: TypeError
你能帮帮我吗?老实说,到目前为止,我只使用super 进行单继承,因此我没有遇到过这样的问题。提前谢谢你。
【问题讨论】:
-
你为什么要在这里模拟任何东西?请注意,在这种情况下,您确实需要始终如一地使用
super、*args和**kwargs,以便使用多重继承。 -
@jonrsharpe 我在嘲笑准备单元测试的事情,因为这个
C.__init__正在做一些事情,这对于确保A.__init__和B.__init__都被正确调用(使用适当的论点).. -
测试行为,而不是实现。测试
C的实例是否正常工作,是否具有正确的属性,而不是它进行的特定调用。测试应该让您确信您的代码在实际使用中可以正常工作并支持重构;您在下面发布的内容直接调用魔术方法并且与实现细节高度耦合,两者都没有。 -
@jonrsharpe 该示例已被最大限度地简化。实际上,
A.__init__和B.__init__更复杂,并且都在(很多)不同的类之后继承。我添加了“我为什么要这样做”的描述,以便每个人都能更好地理解它。我知道这对您来说可能很奇怪,但如果您能帮助我解决我发布的确切问题,我将不胜感激。提前谢谢你。
标签: python-3.x unit-testing mocking pytest