2017-06-19 330 views
1

我有以下的配置類:嘲笑類和返回幾個值

class ConfigB(object): 
    Id = None 
    fileName = None 

    def __init__(self, file): 
    self.Id = self.searchForId(file) 
    self.fileName = file 

這是在下面的類實例化多次和屬性進行訪問:

from config.ConfigB import ConfigB 

class FileRunner(object): 
    def runProcess(self, cfgA) 
    for file in cfgA.listFiles: 
     cfgB = ConfigB(file) 
     print(cfgB.Id) 
     print(cfgB.fileName) 

爲了測試它,我創建以下測試類,我模擬ConfigB爲FileRunner類:

import unittest 
import unittest.mock imort MagicMock 
import mock 
from FileRunner import FileRunner 

class TestFileRunner(unittest.TestCase): 
    @mock.patch('FileRunner.ConfigB') 
    def test_methodscalled(self, cfgB): 

    cfgA = Mock() 
    cfgA.listFiles = ['File1','File2'] 

    cfgB().Id.side_effect = [1,2] 
    cfgB().fileName.side_effect = ['File1','File2'] 

    fileRunner = FileRunner() 


    fileRunner.runProcess(cfgA) 

我正在嘗試爲cfgB獲取模擬以爲'Id'和'fileName'返回多個值。如果我使用cfgB().fileName = 'File1',我可以得到cfgB的模擬函數來返回'File1'兩次,但我更喜歡如果我可以遍歷多個返回值。是可以做的事嗎?

*編輯:我想明確指出,上述測試沒有返回的特定值的工作,而是我得到下面的輸出:

<MagicMock name='cfgB().Id' id='160833320'> 
<MagicMock name='cfgB().fileName' id='160833320'> 
<MagicMock name='cfgB().Id' id='160833320'> 
<MagicMock name='cfgB().fileName' id='160833320'> 
+0

有幾個與你的代碼的問題:1.第一種方法是''__init__''而不是''__Init__' '; 2.''Id''和''FileName''是類變量,可能會導致意外的結果,如[這裏]所述(https://docs.python.org/3.6/tutorial/classes.html#class-and-實例變量)。您可以簡單地刪除它們,只在'__init__''方法中留下分配。這**可能會解決你的問題(我沒有測試過,它可能不是解決方案) –

+0

'__init__'和'__Init__'是一個不在我的實際代碼中的錯字,我在上面修復了它。我不想離開'__init__'方法,因爲實際的'__init__'方法使得我的代碼複雜得多,所以我寧願只模擬返回值。 – EliSquared

回答

1

這裏的問題是,你實際上並沒有使用side_effect它打算使用的方式。

每文檔here,該side_effect屬性狀態:

的函數,只要素被稱爲被調用。請參閱 side_effect屬性。用於引發異常或動態地更改返回值 。該函數的調用與模擬的參數 相同,除非它返回DEFAULT,否則此函數的返回值用作返回值。

這裏要實現的關鍵是函數。這裏的預期實際上是,被稱爲。實際上,您正在測試屬性,並且屬性不會像函數那樣被調用,所以您實際上並未正確地配置測試,因爲您正在使用這些side_effect調用。

根據您要測試的內容,您應該採取稍微不同的方法。看着你的代碼,當你遍歷cfgA.listFiles時,你正在尋找在你的循環內創建一個ConfigB對象。所以,這表明你實際上想要控制side_effect當你打電話ConfigB(file),你在你的測試中假裝修補爲cfgB

此外,你正在通過似乎從cfgA.listFiles迭代到configB迭代的文件名。因此,你可以設置listFiles爲爲任意文件名列表:

cfgA.listFiles = ['some_file_name_1', 'some_file_name_2'] 

然後,所有你需要做的,然後設置你的cfgB模擬的side_effect到現在返回一個包含感興趣的屬性的Mock對象正確繼續與你的測試,例如:

cfgB.side_effect = [ 
    Mock(Id="some_id_1", fileName="some_filename_1"), 
    Mock(Id="some_id_2", fileName="some_filename_2") 
] 

與修改正在運行的話,會產生從你的打印語句您有以下結果代碼:

some_id_1 
some_filename_1 
some_id_2 
some_filename_2 

因此,正如您所看到的,現在我們已經成功設置了您的迭代器,以保存要爲您的測試設置的文件名。此外,side_effect現在正確地用於您的模擬ConfigB,爲了現在返回適當的模擬配置對象,持有您可以在每次迭代中測試的屬性。

這裏是最後的測試方法是什麼樣子都放在一起:

class TestFileRunner(unittest.TestCase): 
    @mock.patch('FileRunner.ConfigB') 
    def test_methodscalled(self, cfgB): 

    cfgA = Mock() 
    cfgA.listFiles = ['some_file_name_1', 'some_file_name_2'] 

    cfgB.side_effect = [ 
     Mock(Id="some_id_1", fileName="some_filename_1"), 
     Mock(Id="some_id_2", fileName="some_filename_2") 
    ] 

    fileRunner = FileRunner() 
    fileRunner.runProcess(cfgA) 
+0

這是我想要做的大部分工作,但現在我有一個最後的問題,因爲這在我的單元測試中引起了意想不到的問題。我在文件runner類'foo(cfgB)'中有一個函數,我想確認是使用正確的模擬函數調用的。如果我做了'foo.assert_called_with(cfgB())',我會得到一個可迭代的錯誤,除非我將第三行添加到'Mock(Id =「some_id_3」,fileName =「some_filename_3」)的side_effect迭代器中。這會引發錯誤,因爲模擬ID現在與assert_called_with語句不同。無論如何參考前兩個嘲笑來確認他們被稱爲? – EliSquared