【问题标题】:Python - create mock test for class method that has context managerPython - 为具有上下文管理器的类方法创建模拟测试
【发布时间】:2015-08-07 04:45:59
【问题描述】:

我正在尝试为具有上下文管理器和许多调用的类函数的方法编写单元测试。我很难理解如何正确模拟该函数,以便我可以测试返回值。我要模拟的类是 db。正如您在下面看到的,我正在使用一个补丁,但我无法弄清楚如何让它返回正确的方法调用。我得到的是一个通用的模拟函数,而不是我期望的返回值。

db_class.py

import db

class Foo():
    def __init__(self):
        pass
    def method(self):
        with db.a() as a:
            b = a.b
            return b.fetch()

unit_db.py

 from mock import Mock, patch, MagicMock
 from db_class import Foo

 @patch('db_class.db')
 def test(db_mock):
     expected_result = [5,10]
     db_mock.return_value = Mock(__enter__ = db_mock,
                                 __exit___ = Mock(),
                                 b = Mock(fetch=expected_result))

     foo = Foo()
     result = foo.method()
     assert result == expected_result

【问题讨论】:

  • 你到底想模拟什么?
  • 您能检查一下您的代码吗? Foo 不是 db 的一部分,而是 db_class 根据你写的。您似乎还试图模拟整个 db 模块,但在您的 with 语句中,您需要模拟 db.a() 方法。最后,您误解了如何在模拟中指定方法。这里 enterexit 是属性,但应该是方法。与 b 规范中的 fetch 相同。
  • @Cilyan - 感谢您的回复 - 我更新了代码以正确导入 Foo 方法您的回复很有意义,我猜首先我应该添加另一个补丁 @patch('db_class .db.a') 被嘲笑。我仍然对如何实现 fetch、enterexit 作为方法与属性感到困惑。
  • @vks 我正在尝试模拟 db_class foo.method。我想检查它返回的结果是否是预期的结果。
  • @Cilyan 好的,我想我已经找到了解决方案,感谢您的见解!

标签: python unit-testing mocking patch with-statement


【解决方案1】:

感谢评论者,我找到了适合我的解决方案。诀窍是修补正确的类,在这种情况下,我想修补 db_class.db.a 而不是 db_class.db。在那之后,确保 fetch() 调用是一种方法很重要(我想我是正确的)。对我来说,这个问题的棘手部分是修补正确的东西以及处理需要一些额外修补的上下文管理器。

@patch('db_class.db.a')
def test(db_a):
    expected_result = [5,10]
    b_fetch = MagicMock()
    b_fetch.fetch.return_value = expected_result 
    db_a.return_value = Mock(b = b_fetch,
                         __enter__= db_a,
                         __exit__ =Mock())
    foo = Foo()
    result = foo.method()
    assert result == expected_result

if __name__ == "__main__":
    test()

【讨论】:

  • 上下文模拟的好方法。
【解决方案2】:

这是相同的测试,使用 pytest 和 mocker fixture:

def test(mocker):
    mock_db = mocker.MagicMock(name='db')
    mocker.patch('db_class.db', new=mock_db)
    expected_result = [5, 10]
    mock_db.a.return_value.__enter__.return_value.b.fetch.return_value = expected_result

    foo = db_class.Foo()
    result = foo.method()
    assert result == expected_result

您可能会发现我编写测试的方式比测试本身更有趣 - 我创建了一个 python library 来帮助我了解语法。

以下是我系统地解决您的问题的方法:

我们从你想要的测试和我的助手库开始:

import db_class

from mock_autogen.pytest_mocker import PytestMocker

def test(mocker):
    # this would output the mocks we need
    print(PytestMocker(db_class).mock_modules().prepare_asserts_calls().generate())

    # your original test, without the mocks
    expected_result = [5,10]
    foo = db_class.Foo()
    result = foo.method()
    assert result == expected_result

现在测试显然失败了(AttributeError: module 'db' has no attribute 'a'),但打印输出很有用:

# mocked modules
mock_db = mocker.MagicMock(name='db')
mocker.patch('db_class.db', new=mock_db)
# calls to generate_asserts, put this after the 'act'
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_db, name='mock_db'))

现在,我将模拟放在调用Foo() 之前和generate_asserts 之后,就在你的断言之前,就像这样(不需要之前的打印,所以我删除了它):

def test(mocker):
    # mocked modules
    mock_db = mocker.MagicMock(name='db')
    mocker.patch('db_class.db', new=mock_db)

    # your original test, without the mocks
    expected_result = [5,10]
    foo = db_class.Foo()
    result = foo.method()

    # calls to generate_asserts, put this after the 'act'
    import mock_autogen
    print(mock_autogen.generator.generate_asserts(mock_db, name='mock_db'))

    assert result == expected_result

现在断言失败 (AssertionError: assert <MagicMock name='db.a().__enter__().b.fetch()' id='139996983259768'> == [5, 10]),但我们再次获得了一些有价值的输入:

mock_db.a.return_value.__enter__.assert_called_once_with()
mock_db.a.return_value.__enter__.return_value.b.fetch.assert_called_once_with()
mock_db.a.return_value.__exit__.assert_called_once_with(None, None, None)

请注意第二行,这几乎就是您需要模拟的内容。稍作改动,它看起来就像mock_db.a.return_value.__enter__.return_value.b.fetch.return_value = expected_result,这样,我们就可以得到最终版本的测试:

def test(mocker):
    mock_db = mocker.MagicMock(name='db')
    mocker.patch('db_class.db', new=mock_db)
    expected_result = [5, 10]
    mock_db.a.return_value.__enter__.return_value.b.fetch.return_value = expected_result

    foo = db_class.Foo()
    result = foo.method()
    assert result == expected_result

您可以添加其他自动生成的断言,或者如果您觉得有用,可以更改它们以包含其他断言。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-22
    • 2022-01-22
    • 1970-01-01
    • 2018-11-16
    • 2011-08-04
    • 2020-05-19
    相关资源
    最近更新 更多