2017-10-04 115 views
0

在我的类下的构造函数中,一个套接字对象被实例化并赋值给一个类成员。我嘲笑了套接字类,并将一个模拟套接字对象设置为套接字构造函数调用的返回值。然后我想断言connect()和sendall()在该对象上被调用。我总是得到assert错误,当我在原始的模拟类对象上声明或者我设置为在构造函数调用时返回的对象时,函数不会被调用。如何对实例化的模拟类对象进行断言

我知道我不能嘲笑的类,它是测试(及其成员),因为这将在这里击败目的。

伪代码:

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. 确保socket库的构造函数被调用合适的参数。

  2. 确保连接()被调用合适的参数。

  3. 确保sendall()被调用,正是我希望它被调用,当我通过邮件到mySocketHandler.write()方法。

回答

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正确的,但在这里它是:通过改变的SocketHandler的__init__

  1. 制作使用依赖注入的采取一个Socket对象,只有实例,如果在ARGS未提供。这样我就可以传入一个模拟或MagicMock对象,并用它来做断言。
  2. 利用一个非常强大的模拟/修补工具MonkeyPatch,它实际上可以修补/模拟类的实例变量。这种做法就像试图用火箭发射器杀死苍蝇一样。
0

你是在正确的轨道上,但有一些事情需要改变,以使测试工作。

问题的一部分直接来自于蝙蝠,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")

  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

这是很多参加,这里是你的测试将是什么样子,如果我写的:

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相似,区别在于它们实现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 
+0

感谢您的回答。这不完全正确,但它帮助我找到正确的答案。对不起,我的语法错误,但是: –

+0

1.您定义了一个方法,以便您不能使用对self的引用,assertIs是unittest.TestCase类中的方法! 2.当我尝试你的代码时,mockSocketClass.return_value.connect.assert_called_with(...)从来没有被调用过。 为什么不使用MagicMock?他们的autospec或spec能力使他们非常有用。 –

+0

对不起,我假设你使用unittest.TestCase并从你的代码中省略了它。 你也可以使用spec和'Mock'。我避免使用它们是因为我很少需要为我定义的任何魔术方法,而且我不希望我的测试在他们不应该通过时通过,因为我忘记了在MagicMock上重写一个魔术方法 – ryanh119