【问题标题】:How can I test methods in a large class with references to `self` using mock?如何使用模拟测试引用“self”的大型类中的方法?
【发布时间】:2021-07-16 02:28:26
【问题描述】:

假设我有一个大类,其中包含许多引用self 的方法。例如:

from unittest import mock
import json
import Gamma


class Alpha:
    def __init__(self):
        self.a = json.loads('file_A')
        self.b = json.loads('file_B')
        self.c = None
        self.d = None
        self.e = Gamma()

    def foo(self):
        json.dumps(self.d)

    def bar(self, dummy):
        self.c = dummy.x
        self.d = dummy.y
        self.foo()


class Beta:
    def __init__(self):
        self.x = json.loads('file_X')
        self.y = json.loads('file_Y')

假设我想使用模拟来测试上述示例中的bar 方法。这就是我正在尝试的,但我显然在这里遗漏了一些东西:

@mock.patch('Beta', autospec=True)
@mock.patch('Alpha', autospec=True)
def test_bar(mock_alpha, mock_beta):
    # Set mock values so the Beta class gets instantiated properly
    mock_beta.x = 8
    mock_beta.y = 9

    # Call the bar method from mocked Alpha class
    mock_alpha.bar(mock_beta)

    # Test whether bar method updated the Alpha class as desired
    assert mock_alpha.c == 8
    assert mock_alpha.foo.called

这些是我得到的两个断言的错误:

>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'c'


>       assert mock_alpha.foo.called
E       AssertionError: assert False
E        +  where False = <MagicMock name='Alpha.foo' spec='function' id='5324111568'>.called
E        +    where <MagicMock name='Alpha.foo' spec='function' id='5324111568'> = <MagicMock name='Alpha' spec='Alpha' id='5323992784'>.foo

如何测试 foo 使用 mock 所做的一切?

PS:这是一个精简的例子;在此特定示例中,在此处模拟 json 对象可能更有意义。但是,在我的实际用例中,Alpha 类非常复杂,要模拟所有端点需要做很多工作。我的问题仅限于是否有一种方法可以像我在示例中尝试(不成功)那样模拟类的实例。

【问题讨论】:

  • __init__ 应该带参数来设置大部分属性;那么你不需要模拟 anything;您只需创建一个适当的实例进行测试。
  • (你仍然可以包装一个Alpha 的实例来测试它的foo 方法是否被调用,但是如果你能找到想要的result 会更好foo 被调用,因为也许实现会在某个时候改变。)

标签: python unit-testing mocking


【解决方案1】:

我将修补json.loads() 方法而不是修补Beta 类。此外,我修补了Alpha 类的.foo() 方法。

例如

main.py:

import json


class Alpha:
    def __init__(self):
        self.a = json.loads('file_A')
        self.b = json.loads('file_B')
        self.c = None
        self.d = None

    def foo(self):
        json.dumps(self.d)

    def bar(self, dummy):
        self.c = dummy.x
        self.d = dummy.y
        self.foo()


class Beta:
    def __init__(self):
        self.x = json.loads('file_X')
        self.y = json.loads('file_Y')

test_main.py:

from unittest import mock
import unittest
from main import Alpha, Beta


class TestAlpha(unittest.TestCase):
    @mock.patch('main.json.loads')
    @mock.patch('main.Alpha.foo')
    def test_bar(self, mocked_foo, mocked_json_loads):
        def json_loads_side_effect(s):
            if s == 'file_X':
                return 'a'
            if s == 'file_Y':
                return 'b'

        mocked_json_loads.side_effect = json_loads_side_effect
        alpha = Alpha()
        beta = Beta()
        alpha.bar(beta)
        self.assertEqual(alpha.c, 'a')
        self.assertEqual(alpha.d, 'b')
        mocked_foo.assert_called_once()


if __name__ == '__main__':
    unittest.main()

测试结果:

.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Name                                      Stmts   Miss  Cover   Missing
-----------------------------------------------------------------------
src/stackoverflow/68402729/main.py           17      1    94%   12
src/stackoverflow/68402729/test_main.py      21      0   100%
-----------------------------------------------------------------------
TOTAL                                        38      1    97%

【讨论】:

  • 你是对的,对于这个例子来说这更有意义。我应该更清楚这一点。这个例子只是一个概念证明,在我的课堂实际课堂中,有太多的端点要模拟。我想知道是否有一种方法可以通过模拟类实例来实现模拟?
  • @slideshop2 您为我的用例发布的解决方案的问题是alpha = Alpha() 没有足够的上下文来正确执行任何方法。添加/模拟所有必需的上下文将是很多工作。
【解决方案2】:

研究了一段时间并考虑了@slideshowp2 对这个问题的回答。我找到了一个最接近我正在寻找的解决方案。似乎我最初模拟整个类 Alpha 的想法是不可行的,相反我可以模拟 Alpha 的任何端点,但需要以一种或另一种方式实例化 Alpha

这是我的最终解决方案。


@pytest.fixture
def set_up_alpha():
    """
    Use this fixture to set-up an instance of Alpha object
    Mock any endpoints as necessary
    """
    with mock.patch.object(json, 'loads') as mock_json_loads:
        mock_json_loads.side_effect = lambda x: "test_string"
        a = Alpha()

        yield a


@mock.patch.object(Alpha, 'foo', autospec=True)
def test_bar(mock_foo, set_up_alpha):
    """
    GIVEN an Alpha object
        AND a mocked Beta object
    WHEN the bar method is invoked
    THEN corresponding instances of Alpha object are updated
        AND method foo is called
        AND the method does not return anything
    """

    # Use a mocked object for Beta instance
    class MockBeta:
        x = "test_x"
        y = "test_y"

    # Instantiate an Alpha object
    a = set_up_alpha

    output = a.bar(MockBeta)

    # Test whether bar method updated the Alpha class as desired
    assert a.c == "test_x"
    assert a.d == "test_y"
    assert mock_foo.called
    assert output is None

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-05
    • 2018-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多