【问题标题】:Unit test (in pytest) for multiple super inheritance多重超级继承的单元测试(在pytest中)
【发布时间】: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


【解决方案1】:

到目前为止我管理的第一个可行的解决方案:
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 MagicMock, patch

from inheritance import C


class TestC:
    def setup(self):
        self.mock_c_object = MagicMock(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)

您还有其他建议吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-03
    • 1970-01-01
    • 2011-04-07
    • 1970-01-01
    • 2018-04-22
    • 2015-12-24
    • 2021-10-19
    相关资源
    最近更新 更多