如何对实例化的mock类对象进行断言

时间:2017-10-04 16:00:47

标签: python python-3.x python-mock

在我测试的类的构造函数中,实例化套接字对象并将其分配给类成员。我模拟了套接字类并将一个模拟的套接字对象设置为套接字构造函数调用的返回值。然后我想断言在该对象上调用connect()和sendall()。我总是得到断言错误,当我在原始的模拟类对象上断言或者我设置为在构造函数调用时返回时,函数不被调用。

我知道我不能嘲笑那些受到考验的班级(以及它的成员),因为这会破坏这里的目的。

伪代码:

import socket

Class socketHandler():
    def __init__(...):
    self.mySocket = socket(...)
    ...
    self.mySocket.connect(...)

    def write(message):
        self.mySocket.sendall(message)

测试:

from unittest import mock
from unittest.mock import MagicMock #not sure if i need this
import pytest
import socketHandler

@mock.patch(socketHandler.socket)
def test_socket_handler(mockSocket):
    ...
    new_sock = mock_socket()
    mock_socket.return_value = new_sock

    mySocketHandler = SocketHandler(...)

    mock_socket.socket.assert_called_with(...)
    new_sock.connect.assert_called_with(...) #fails (never called)
    mock_socket.connect.assert_called_with(...) #fails (never called)
    #likewise for the sendall() method call when mysocketHandler.write(..)
    #is called

此测试的目的是:

  1. 确保使用正确的参数调用套接字库的构造函数。

  2. 确保使用正确的参数调用connect()。

  3. 当我将消息传递给mySocketHandler.write()方法时,确保调用sendall()时正好调用它。

2 个答案:

答案 0 :(得分:1)

完整答案来自@ ryanh119和此帖link提供的提示

我将修复ryanh119上面给出的例子并且不要编辑我搞砸的原始问题,所以为了完整性:

from unittest import mock
import pytest
import socketHandler

@mock.patch("app_directory.socketHandler.socket")
def test_socket_handler(mockSocketClass):

# mockSocketClass is already a mock, so we can call production right away.
mySocketHandler = SocketHandler(...)

# Constructor of mockSocketClass was called, since the class was imported
#like: import socket we need to:
mockSocketClass.socket.assert_called_with(...)

# Production called connect on the class instance variable
# which is a mock so we can check it directly.
# so lets just access the instance variable sock
mySocketHandler.mySocket.connect.assert_called_with(...)

# The same goes for the sendall call:
mySocketHandler.mySocket.sendall.assert_called_with(expectedMessage)

我也做了一些研究,我想提到另外两个解决方案。它们不像上面那样pythonicaly正确,但它是:

  1. 通过更改socketHandler的__init__以接受套接字对象并仅在args中提供的情况下实例化它来利用依赖注入。这样我就可以通过模拟或MagicMock对象传递并使用它来执行断言。
  2. 使用名为MonkeyPatch的非常强大的模拟/修补工具,它实际上可以修补/模拟类的实例变量。这种方法就像试图用火箭发射器杀死苍蝇一样。

答案 1 :(得分:0)

你走在正确的轨道上,但是有一些事情需要改变才能使这个测试起作用。

问题的一部分就是patch传递给您的测试方法的模拟被称为mockSocket,但您的测试代码指的是mock_socket。< / p>

此外,patch的第一个参数,即要修补的内容,应该是要修补内容的模块路径的字符串表示。如果您的文件结构如下所示:

|-- root_directory
|   |
|   |-- app_directory
|   |   |-- socketHandler.py
|   |   `-- somethingElse.py
|   |
|   `-- test_directory
|       |-- testSocketHandler.py
|       `-- testSomethingElse.py

并且您从根目录运行测试,您需要调用这样的补丁:@mock.patch("app_directory.socketHandler.socket")

  1. 调用构造函数 - 最重要的事情是mockSocket是一个Mock对象,表示套接字。因此,要测试构造函数是否被调用,您需要检查mockSocket.assert_called_with(...)。如果您的作品调用socket(...),那将会通过。

    您可能还想声明mySocketHandler.socketmockSocket.return_value是同一个对象,以测试mySocketHandler不仅会调用构造函数,还会将其分配给正确的属性。

  2. 和3. connectsendall被正确调用 - 你永远不应该在测试中调用你的mock,因为它可能导致错误地传递断言。换句话说,您希望生产代码是唯一调用模拟的东西。这意味着你不应该使用行new_sock = mock_socket(),因为无论你的生产代码是什么,你之前关于构造函数的断言都会传递,我认为这会导致你的其他断言失败。

    mockSocket已经是Mock的一个实例,因此它的返回值将自动成为另一个不同的Mock实例。因此,您不需要上面测试代码的前两行,并且只需要connect上的一个断言。同样的想法适用于sendall

  3. 这是很多东西,如果我写的话,这就是你的测试的样子:

    from unittest import mock
    import pytest
    import socketHandler
    
    @mock.patch("app_directory.socketHandler.socket")
    def test_socket_handler(mockSocketClass): # renamed this variable to clarify that it's a mock of a class.
    
        # mockSocketClass is already a mock, so we can call production right away.
        mySocketHandler = SocketHandler(...)
    
        # Constructor of mockSocketClass was called
        mockSocketClass.assert_called_with(...)
    
        # Instance of mockSocketClass was assigned to correct attribute on SocketHandler
        self.assertIs(mockSocketClass.return_value, mySocketHandler.socket)
    
        # Production called connect on the return_value of the mock module, i.e. the instance of socket.
        mockSocketClass.return_value.connect.assert_called_with(...)
    
        # If SocketHandler's constructor calls sendall:
        mockSocketClass.return_value.sendall.assert_called_with(expectedMessage)
    

    奖金回合! MagicMock的行为类似于Mock s,但它们会为some magic methods实现一些默认值。除非我绝对需要,否则我不会使用它们。这是一个例子:

    from mock import Mock, MagicMock
    
    mock = Mock()
    magic_mock = MagicMock()
    
    int(mock)
    >>>Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: int() argument must be a string or a number, not 'Mock'
    
    len(mock)
    >>>Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: object of type 'Mock' has no len()
    
    int(magic_mock)
    >>> 1
    
    len(magic_mock)
    >>> 0