2017-10-04 116 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