如何对实例化的模拟 class 对象进行断言
How to do assert on instantiated mock class object
在我的 class 测试中的构造函数中,套接字对象被实例化并分配给 class 成员。我模拟了套接字 class 并将模拟套接字对象设置为 return 值以调用套接字构造函数。然后我想断言在该对象上调用了 connect() 和 sendall() 。当我在原始模拟 class 对象或我在构造函数调用时设置为 return 的对象上断言时,我总是得到断言错误,即函数没有被调用。
我知道我不能嘲笑正在测试的 class(及其成员),因为那样会破坏这里的目的。
伪代码:
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
本次测试的目的是:
确保使用正确的参数调用套接字库的构造函数。
确保使用正确的参数调用 connect()。
确保当我将消息传递到 mySocketHandler.write() 方法时,调用 sendall() 的方式正是我希望调用的方式。
您走在正确的轨道上,但需要更改一些内容才能使此测试生效。
您的问题的一部分是 patch
传递到您的测试方法的模拟称为 mockSocket
,但您的测试代码指的是称为 mock_socket
的东西.
此外,patch
的第一个参数,即您要修补的内容,应该是您要修补的模块路径的 字符串表示形式 某物。如果您的文件结构如下所示:
|-- root_directory
| |
| |-- app_directory
| | |-- socketHandler.py
| | `-- somethingElse.py
| |
| `-- test_directory
| |-- testSocketHandler.py
| `-- testSomethingElse.py
而你运行你的测试来自根目录,你想像这样调用补丁:@mock.patch("app_directory.socketHandler.socket")
构造函数被调用 - 最重要的是要意识到mockSocket
是一个Mock
对象代表套接字class。所以要测试调用了构造函数,您需要检查 mockSocket.assert_called_with(...)
。如果您的产品调用 socket(...)
.
,那将通过
您可能还想断言 mySocketHandler.socket
与 mockSocket.return_value
是同一个对象,以测试 mySocketHandler 不仅调用构造函数,而且将其分配给正确的属性。
和 3. connect
和 sendall
被正确调用 - 你永远不应该在测试中调用你的模拟,因为它会导致错误地通过断言。换句话说,您希望您的生产代码是唯一调用模拟的东西。这意味着你不应该使用行 new_sock = mock_socket()
,因为无论你的生产代码做什么,你之前关于构造函数的断言都会通过,我认为它会导致你的其他断言失败。
mockSocket
已经是 Mock
的实例,因此它的 return 值将自动成为另一个不同的 Mock
实例。因此,您不需要上面的测试代码的前两行,您只需要 connect
上的一个断言。同样的想法适用于 sendall
.
要吸收的东西太多了,如果我这样写,你的测试会是这样的:
from unittest import mock, TestCase
import pytest
import socketHandler
class TestSocketHandler(TestCase):
@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
s 的行为类似于 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
根据@ryanh119 和此 post
给出的提示得出的完整答案
我将修复上面由 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)
我也做了一些研究,我想提一下另外两个解决方案。它们不像上面的那样 pythonically 正确,但这里是:
- 通过更改 socketHandler 的
__init__
以接收套接字对象并仅在未在 args 中提供时实例化它来使用依赖注入。这样我就可以传入模拟或 MagicMock 对象并使用它来进行断言。
- 使用名为 MonkeyPatch 的极其强大的 mocking/patching 工具,它实际上可以 patch/mock 类 的实例变量。这种方法就像试图用火箭发射器杀死苍蝇一样。
在我的 class 测试中的构造函数中,套接字对象被实例化并分配给 class 成员。我模拟了套接字 class 并将模拟套接字对象设置为 return 值以调用套接字构造函数。然后我想断言在该对象上调用了 connect() 和 sendall() 。当我在原始模拟 class 对象或我在构造函数调用时设置为 return 的对象上断言时,我总是得到断言错误,即函数没有被调用。
我知道我不能嘲笑正在测试的 class(及其成员),因为那样会破坏这里的目的。
伪代码:
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
本次测试的目的是:
确保使用正确的参数调用套接字库的构造函数。
确保使用正确的参数调用 connect()。
确保当我将消息传递到 mySocketHandler.write() 方法时,调用 sendall() 的方式正是我希望调用的方式。
您走在正确的轨道上,但需要更改一些内容才能使此测试生效。
您的问题的一部分是 patch
传递到您的测试方法的模拟称为 mockSocket
,但您的测试代码指的是称为 mock_socket
的东西.
此外,patch
的第一个参数,即您要修补的内容,应该是您要修补的模块路径的 字符串表示形式 某物。如果您的文件结构如下所示:
|-- root_directory
| |
| |-- app_directory
| | |-- socketHandler.py
| | `-- somethingElse.py
| |
| `-- test_directory
| |-- testSocketHandler.py
| `-- testSomethingElse.py
而你运行你的测试来自根目录,你想像这样调用补丁:@mock.patch("app_directory.socketHandler.socket")
构造函数被调用 - 最重要的是要意识到
,那将通过mockSocket
是一个Mock
对象代表套接字class。所以要测试调用了构造函数,您需要检查mockSocket.assert_called_with(...)
。如果您的产品调用socket(...)
.您可能还想断言
mySocketHandler.socket
与mockSocket.return_value
是同一个对象,以测试 mySocketHandler 不仅调用构造函数,而且将其分配给正确的属性。和 3.
connect
和sendall
被正确调用 - 你永远不应该在测试中调用你的模拟,因为它会导致错误地通过断言。换句话说,您希望您的生产代码是唯一调用模拟的东西。这意味着你不应该使用行new_sock = mock_socket()
,因为无论你的生产代码做什么,你之前关于构造函数的断言都会通过,我认为它会导致你的其他断言失败。mockSocket
已经是Mock
的实例,因此它的 return 值将自动成为另一个不同的Mock
实例。因此,您不需要上面的测试代码的前两行,您只需要connect
上的一个断言。同样的想法适用于sendall
.
要吸收的东西太多了,如果我这样写,你的测试会是这样的:
from unittest import mock, TestCase
import pytest
import socketHandler
class TestSocketHandler(TestCase):
@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
s 的行为类似于 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
根据@ryanh119 和此 post
我将修复上面由 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)
我也做了一些研究,我想提一下另外两个解决方案。它们不像上面的那样 pythonically 正确,但这里是:
- 通过更改 socketHandler 的
__init__
以接收套接字对象并仅在未在 args 中提供时实例化它来使用依赖注入。这样我就可以传入模拟或 MagicMock 对象并使用它来进行断言。 - 使用名为 MonkeyPatch 的极其强大的 mocking/patching 工具,它实际上可以 patch/mock 类 的实例变量。这种方法就像试图用火箭发射器杀死苍蝇一样。