2016-02-26 124 views
2

比方說,我要測試的這款哦,所以,複變函數:嘲諷FTP在單元測試

def func(hostname, username, password): 
    ftp = FTP(hostname, username, password) 
    ftp.retrbinary('RETR README', open('README', 'wb').write) 

一項測試是:

@patch('FTP') 
def test_func_happy_path(): 
    mock_ftp = Mock() 
    mock_ftp.retrbinary = Mock() 
    MockFTP.return_value = mock_ftp() 
    func('localhost', 'fred', 's3Kr3t') 
    assert mock_ftp.retrbinary.called 

然而,這將創建一個本地文件稱爲README,我顯然不想要。

有沒有辦法模擬/修補open這樣就不會創建文件?

顯然,作爲工作的時候,我可以確保該文件被寫入temporary directory,我可以作爲參數傳遞給func或內func和回報創造。

注意,使用裝飾@patch('__builtin__.open'),以下預期上升:

self = <Mock name=u'open()' spec='FTP' id='51439824'>, name = 'write' 
    def __getattr__(self, name): 
     if name in ('_mock_methods', '_mock_unsafe'): 
      raise AttributeError(name) 
     elif self._mock_methods is not None: 
      if name not in self._mock_methods or name in _all_magics: 
>    raise AttributeError("Mock object has no attribute %r" % name) 
AttributeError: Mock object has no attribute 'write' 

我傳遞一個回調ftp.retrbinary,而不是一個函數調用。

+0

或者代替書寫到一個臨時目錄,你可以使用[StringIO的(https://docs.python.org/2/library/stringio.html)。避免寫入磁盤並將其全部保存在內存中。我認爲那是你的目標,對嗎? – idjaw

+0

是的,我實際上指的是使用'StringIO'代替開放的測試。這樣你就避免了爲單元測試寫入磁盤。 – idjaw

+0

我正在按我的方式查看您尋找的解決方案! :) *到編碼機器* ... – idjaw

回答

2

所以,考慮到你不關心你的開放會發生什麼,你可以直接嘲笑它,所以它停止寫作。要做到這一點,你可以按照類似的方法來模仿你的FTP。因此,考慮到這一點,你可以設置你的測試代碼是這樣的:

import unittest 
from mock import patch, Mock 
from my_code import func 

class SirTestsAlot(unittest.TestCase): 

    @patch('my_code.open') 
    @patch('my_code.FTP') 
    def test_func_happy_path(self, MockFTP, m_open): 
     MockFTP.return_value = Mock() 
     mock_ftp_obj = MockFTP() 

     m_open.return_value = Mock() 

     func('localhost', 'fred', 's3Kr3t') 

     assert mock_ftp_obj.retrbinary.called 
     assert m_open.called 
     # To leverage off of the other solution proposed, you can also 
     # check assert called with here too 
     m_open.assert_called_once_with('README', 'wb') 


if __name__ == '__main__': 
    unittest.main() 

正如你可以看到,我們在這裏做的是,我們對於嘲諷的地方,我們正在測試。所以,考慮到這一點,我們嘲笑openFTP相對於my_code

現在內my_code,什麼也沒有改變:

from ftplib import FTP 


def func(hostname, username, password): 
    ftp = FTP(hostname, username, password) 

    ftp.retrbinary('RETR README', open('README', 'wb').write) 

運行該測試套件回來成功。

+0

這工作正常!超。我會等待一段時間來接受它,以防萬一別人想出一些黑巫術魔法......^_〜 – Sardathrion

+0

@Sardathrion真棒!很高興爲你工作。祝你工作順利。 – idjaw

+0

@Sardathrion有什麼特別的你想要做什麼?我可以嘗試一下。 :P – idjaw

2

另一種方法包括使用mock_open

from unittest.mock import patch, mock_open 
import ftplib 


def func(hostname, username, password): 
    ftp = ftplib.FTP(hostname, username, password) 
    ftp.retrbinary('RETR README', open('README', 'wb').write) 


@patch('ftplib.FTP') 
def test_func_happy_path(MockFTP): 
    mock_ftp = MockFTP.return_value # returns another `MagicMock` 
    with patch('__main__.open', mock_open(), create=True) as m: 
     func('localhost', 'fred', 's3Kr3t') 

    assert mock_ftp.retrbinary.called 
    m.assert_called_once_with('README', 'wb') 


test_func_happy_path()