2012-07-08 37 views
5

我使用unittest在Python中進行單元測試。據我所知,unittest在每次測試之前調用setUp函數,以便單元測試對象的狀態是相同的,並且執行測試的順序並不重要。Python中的單元測試對象 - 對象不會寫在設置中

現在我有這個類我測試...

#! usr/bin/python2 

class SpamTest(object): 

    def __init__(self, numlist = []): 
     self.__numlist = numlist 

    @property 
    def numlist(self): 
     return self.__numlist 

    @numlist.setter 
    def numlist(self, numlist): 
     self.__numlist = numlist 

    def add_num(self, num): 
     self.__numlist.append(num) 

    def incr(self, delta): 
     self.numlist = map(lambda x: x + 1, self.numlist) 

    def __eq__(self, st2): 
     i = 0 
     limit = len(self.numlist) 

     if limit != len(st2.numlist): 
      return False 

     while i < limit: 
      if self.numlist[i] != st2.numlist[i]: 
       return False 

      i += 1 

     return True 

具有以下的單元測試...

#! usr/bin/python2 

from test import SpamTest 

import unittest 

class Spammer(unittest.TestCase): 

    def setUp(self): 
     self.st = SpamTest() 
     #self.st.numlist = [] <--TAKE NOTE OF ME! 
     self.st.add_num(1) 
     self.st.add_num(2) 
     self.st.add_num(3) 
     self.st.add_num(4) 

    def test_translate(self): 
     eggs = SpamTest([2, 3, 4, 5]) 
     self.st.incr(1) 
     self.assertTrue(self.st.__eq__(eggs)) 

    def test_set(self): 
     nl = [1, 4, 1, 5, 9] 
     self.st.numlist = nl 
     self.assertEqual(self.st.numlist, nl) 

if __name__ == "__main__": 
    tests = unittest.TestLoader().loadTestsFromTestCase(Spammer) 
    unittest.TextTestRunner(verbosity = 2).run(tests) 

這個測試失敗了test_translate。

我可以做兩件事情,使測試成功:

(1)取消註釋設置功能的第二行。或者,

(2)更改測試名稱,使translate首先出現。我注意到unittest按字母順序執行測試。將translate更改爲atranslate,以便它首先執行使所有測試都成功。

對於(1),我無法想象這是如何影響測試的,因爲在setUp的第一行,我們爲self.st創建了一個新對象。至於(2),我的投訴是相似的,因爲,嘿,在setUp我分配一個新的對象self.st所以我所做的任何self.sttest_set應該不會影響test_translate的結果。

那麼,我在這裏錯過了什麼?

回答

10

沒有研究你的解決方案的詳細信息,你應該閱讀Fredrik Lundh的Default Parameter Values in Python

這很可能是因爲您的空列表作爲默認參數解釋了您的問題。原因是該列表僅在第一次是空的,除非您稍後明確地將其清空。初始的空白默認列表是在沒有顯式參數傳遞時重用的列表類型的單個實例。

閱讀上述文章以解決您對默認參數的思考是個好主意。原因是合乎邏輯的,但可能是意想不到的。

的一般建議解決辦法是使用None__init__的默認值,並設置空單體內,如果該參數不通過,像這樣:

class SpamTest(object): 

    def __init__(self, numlist=None): 
     if numlist is None: 
      numlist = []   # this is the new instance -- the empty list 
     self.__numlist = numlist 
+0

所以......再教育我一點。 'None'和'== None'有什麼區別?我總是使用'是None';不知道'== None'的作品。 – skytreader 2012-07-08 15:21:24

+1

@skytreader:運算符'is'測試對象身份。 「None」值由NoneType類的單個實例表示。值爲'None'意味着您將共享對同一對象的引用。 'numlist是None'表示您正在測試是否共享相同的對象。 == ==運算符更復雜。如果'numlist'是定義自己的'.__ eq__'方法的類的實例(引用),'=='可能會產生一個意外的布爾值。但是,簡單情況下大致相同。 – pepr 2012-07-08 15:40:12

4

這是由於當使用像列表這樣的可變對象時,默認參數在Python中的行爲方式:Default Parameter Values in Python

在行:

def __init__(self, numlist = []): 

爲numlist的默認參數只計算一次,所以你只需要一個橫跨所述SpamTest類的所有實例共享的列表中的一個實例。

因此,即使每次測試都要求測試setUp,它永遠不會創建一個新的空列表,並且對列表實例起作用的測試最終會逐步踩到其他腳趾。

修復的方法是有這樣的事情,而不是使用非可變對象像None

def __init__(self, numlist = None): 
    if numlist is None: 
     numlist = [] 
    self.__numlist = numlist 

設置屬性時,它的工作原理的原因是,你提供一個全新的空列表在那裏,替換在構造函數中創建的列表。

+0

哦,該死。我的+1> :)看起來好像我複製/粘貼您的解決方案。但它確實只是巧合。可能只有'numlist是None'會更好。 – pepr 2012-07-08 09:45:07

+0

@pepr沒有任何問題+1對我來說也是好的形式,正確的答案也是:) Fredrik Lundh的文章確實非常棒,所以我們都沒有提到它。 – 2012-07-08 09:46:40

相關問題