【问题标题】:Testing that a method in instance has been called in mock测试实例中的方法是否已在模拟中调用
【发布时间】:2018-05-16 08:19:47
【问题描述】:

我有这种设置,我正在测试一个正在使用另一个类的类,我想模拟后者,所以我只测试第一个类本身。

nuclear_reactor.py:

class NuclearReactor():
    def __init__(self):
        print "initializing the nuclear reactor"

    def start(self):
        print "starting the nuclear reactor"

nuclear_manager.py:

from nuclear_reactor import NuclearReactor

class NuclearManager():
    def __init__(self):
        print "manager creating the nuclear reactor"
        self.reactor = NuclearReactor()

    def start(self):
        print "manager starting the nuclear reactor"
        self.reactor.start()

test_nuclear_manager.py:

from mock import Mock
import nuclear_manager
from nuclear_manager import NuclearManager

def test():
    mock_reactor = nuclear_manager.NuclearReactor = Mock()
    nuke = NuclearManager()
    nuke.start()
    nuke.start()
    print mock_reactor.mock_calls
    print mock_reactor.start.call_count

test()

我想测试的是 NuclearReactor.start 被调用, 但是当我运行它时,我得到:

manager creating the nuclear reactor
manager starting the nuclear reactor
manager starting the nuclear reactor
[call(), call().start(), call().start()]
0

我完全理解,因为start 是实例的属性而不是类的属性,我可以解析mock_calls,但是没有更好的方法来检查实例化模拟类的调用是造出来的?

我可以在NuclearManager 中使用依赖注入来传递一个模拟NuclearReactor,但我认为还有另一种只使用模拟的方法。

【问题讨论】:

  • 为什么你给经理打了两次电话,.start() 却被打了一次?
  • 是的,您应该将一个实例注入NuclearManager,因为您的代码现在使管理器将其reactor 实例设置为默认值。只有程序的最高级别部分应该有默认值。
  • @quamrana:OP 是否应该使用依赖注入是一个完全独立的讨论。并非所有被测代码都适合依赖注入。

标签: python python-2.7 unit-testing mocking


【解决方案1】:

您确实在测试start 是否已在类上直接调用,而您的代码没有。可以直接在实例上测试方法;请记住,通过调用类来生成实例:

print mock_reactor.return_value.calls
print mock_reactor.return_value.start.call_count

Mock.return_value attribute 是调用模拟类的结果,因此是实例。

您也可以只调用模拟。默认情况下,Mocks 在调用时总是返回完全相同的对象,一个表示该返回值的新模拟:

print mock_reactor().calls
print mock_reactor().start.call_count

调用mock实例的结果,和mock实例return_value属性是一回事。

通过打印对NuclearReactor 模拟的调用,您已经走在正确的道路上,您只是错过了在调用模拟上调用start() 的细节,所以call().start(),未记录 start()

您可能希望使用mock.patch() 来处理补丁,而不是直接分配;这可以确保补丁再次删除,以便其他测试可以自行决定要模拟的内容:

import mock
from nuclear_manager import NuclearManager

@mock.patch('nuclear_manager.NuclearReactor')
def test(mock_reactor):
    nuke = NuclearManager()
    nuke.start()
    nuke.start()

    instance = mock_reactor.return_value
    assert instance.start.call_count == 2
    instance.assert_called()

我在这里用它作为装饰器;当调用test() 函数时,mock 就位,当函数退出时,它又被移除。您还可以使用patch() 作为上下文管理器来更精细地限制补丁的范围。

另外,对于这样的单元测试,请使用unittest library

import mock
import unittest
import nuclear_manager

class NuclearManagerTests(unittest.TestCase):
    @mock.patch('nuclear_manager.NuclearReactor')
    def test_start(self, mock_reactor):
        nuke = NuclearManager()
        nuke.start()
        nuke.start()

        instance = mock_reactor.return_value
        self.assertEqual(instance.start.call_count, 2)
        instance.assert_called()

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

这使您可以将测试放入更大的测试套件中,启用和禁用测试,并与其他测试工具集成。

【讨论】:

  • 不是我自己使用patch,但很高兴知道@patch()稍后会恢复范围。
  • 非常感谢 Marijn,有道理。请注意,我使用的是pytest,所以不需要unittest 额外的行李。 assert 就是我所需要的。
【解决方案2】:

我使用 mocks 的方式是这样的:(代码是 Python 3)

from unittest.mock import MagicMock

class NuclearManager():
    def __init__(self, reactor):
        print("manager creating the nuclear reactor")
        self.reactor = reactor

    def start(self):
        print("manager starting the nuclear reactor")
        self.reactor.start()


def test():
    mock_reactor = MagicMock()
    nuke = NuclearManager(mock_reactor)
    nuke.start()
    nuke.start()

    # These two prints would actually be asserts of some sort
    print(mock_reactor.mock_calls)
    print(mock_reactor.start.call_count)

test()

输出:

manager creating the nuclear reactor
manager starting the nuclear reactor
manager starting the nuclear reactor
[call.start(), call.start()]
2

【讨论】:

  • OP 有一个假设的情况。并非所有代码都需要注入,您仍然可以轻松访问实例,因为这是对类的调用
猜你喜欢
  • 2018-02-28
  • 2019-01-16
  • 2014-05-05
  • 2013-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多