我使用Python 2.7和模拟库来测试是否使用某些参数调用connect
类的对象实例的socket.socket
方法。但是我想将模拟仅用作"标记"并继续正常的函数调用。在下面的情况中,我希望socket.socket.connect
调用真正的非修补函数作为"副作用"所以这个方法以后不会失败。
也就是说,我希望模拟socket.socket
类保持相同的功能和行为,但具有记录调用的额外功能。
这是(简化)测试。这就是我认为我错了:
# test.py
@patch('socket.socket.connect')
@override_settings(SERVER_IP='127.0.0.1')
def test_ip_from_settings(self, connect_mock):
"""
The IP to connect to is taken from the Django settings.
"""
def connect(self, address):
socket.socket.connect(self, address)
connect_mock.side_effect = connect
result = connections.get_result()
connect_mock.assert_called_with(('127.0.0.1', TCP_PORT))
作为参考,这是连接和检索结果的(再次简化的)代码:
# connections.py
from django.conf import settings
def get_result():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((settings.SERVER_IP, TCP_PORT))
query = 'myquery'
s.sendall(query)
result = s.recv(BUFFER_SIZE)
return result
然而,当运行测试时TypeError: connect() takes exactly 2 arguments (1 given)
被引发。我怎么能做我想做的事?
答案 0 :(得分:1)
要mocking unbound method,您必须使用autospec=True
来保留签名....但遗憾的是socket.socket.connect()
无法模仿because is a C method。但我们的目标不是嘲笑它,而是用模拟包装它。所以我发现最简单但不是很干净的技巧是使用一个新类来欺骗模拟框架
from mock import ANY
class MyS():
def connect(self, address): #The signature
pass
@patch("socket.socket.connect", autospec=MyS.connect, side_effect=socket.socket.connect)
@override_settings(SERVER_IP='127.0.0.1')
def test_ip_from_settings(self, connect_mock):
"""
The IP to connect to is taken from the Django settings.
"""
result = connections.get_result()
connect_mock.assert_called_with(ANY,('127.0.0.1', TCP_PORT))
您必须使用模拟助手中的ANY
,因为您不知道将哪个套接字对象传递给您的包装器。
这个技巧适用于Python3和Python2.7,但在Python3中,行为略有不同,因为socket.socket.connect()
不是函数而是method_descriptor
。
>>> import socket
>>> type(socket.socket.connect)
<class 'method_descriptor'>
同样在这种情况下,使用autospec=True
无效。
真正的问题是:您确定需要真实连接才能进行测试。模拟目标是从真实资源中解开测试,注册调用和参数断言是一个优点,但第一个用途是通过简单,快速的模拟替换真实对象,并且可以配置为返回我们测试定义行为所需的内容。
也许您真正需要的是patch()
socket.socket
并设置一些return_values
或side_effect
以便在您要测试的情况下开展测试。
答案 1 :(得分:0)
方法connect
内的内部函数test_ip_from_settings
不是方法,而是函数。因此,您必须删除第一个参数self
。
此:
def connect(self, address):
socket.socket.connect(self, address)
应该是:
def connect(address):
socket.socket.connect(address)
这是因为当你调用s.connect((settings.SERVER_IP, TCP_PORT))
时,元组(settings.SERVER_IP, TCP_PORT)
是一个参数,在你的情况下转到self
参数,然后address
变量仍然需要要指定。
在第二个正确的情况下,没有self
参数,因此元组绑定到address
参数。