2012-09-28 78 views
8

我有困難的測試Python函數的返回 可迭代的,就像是函數返回迭代 屈服或簡單地返回一個迭代,就像return imap(f, some_iter)return permutations([1,2,3])功能。測試功能在Python

因此,對於排列示例,我期望函數的輸出爲[(1, 2, 3), (1, 3, 2), ...]。所以,我開始測試我的代碼。

def perm3(): 
    return permutations([1,2,3]) 

# Lets ignore test framework and such details 
def test_perm3(): 
    assertEqual(perm3(), [(1, 2, 3), (1, 3, 2), ...]) 

這是行不通的,因爲perm3()是一個迭代,而不是 列表。所以我們可以修復這個特殊的例子。

def test_perm3(): 
    assertEqual(list(perm3()), [(1, 2, 3), (1, 3, 2), ...]) 

這工作正常。但是如果我有嵌套迭代器呢?這是 iterables產生iterables?就像表達式 product(permutations([1, 2]), permutations([3, 4]))一樣。現在這是 可能沒有用,但很明顯,它將是(一旦展開 迭代器),如[((1, 2), (3, 4)), ((1, 2), (4, 3)), ...]。 但是,我們不能只圍繞我們的結果包裹list,因爲只有 將iterable<blah>轉換爲[iterable<blah>, iterable<blah>, ...]。那麼當然 我可以做map(list, product(...)),但只針對2

所以一個 嵌套級別的作品,並蟒蛇測試社區有對 問題的任何解決方案測試iterables什麼時候?當然,有些iterables不能以這種方式進行測試,就像你想要一個無限生成器一樣,但是 這個問題應該足夠普遍,以至於有人想到 。

回答

4

我用KennyTM's assertRecursiveEq

import unittest 
import collections 
import itertools 

class TestCase(unittest.TestCase): 
    def assertRecursiveEq(self, first, second, *args, **kwargs): 
     """ 
     https://stackoverflow.com/a/3124155/190597 (KennyTM) 
     """ 
     if (isinstance(first, collections.Iterable) 
      and isinstance(second, collections.Iterable)): 
      for first_, second_ in itertools.izip_longest(
        first, second, fillvalue = object()): 
       self.assertRecursiveEq(first_, second_, *args, **kwargs) 
     else: 
      # If first = np.nan and second = np.nan, I want them to 
      # compare equal. np.isnan raises TypeErrors on some inputs, 
      # so I use `first != first` as a proxy. I avoid dependency on numpy 
      # as a bonus. 
      if not (first != first and second != second): 
       self.assertAlmostEqual(first, second, *args, **kwargs)     

def perm3(): 
    return itertools.permutations([1,2,3]) 

class Test(TestCase): 
    def test_perm3(self): 
     self.assertRecursiveEq(perm3(), 
      [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]) 

if __name__ == '__main__': 
    import sys 
    sys.argv.insert(1, '--verbose') 
    unittest.main(argv = sys.argv) 
+0

我會接受這個,因爲它和dbw的答案一樣好。與我的答案不同,你可以混合使用'tuples' /'lists'。這個答案也只是複製粘貼可運行。 :)但是! (1)實際檢查它是否返回一個可迭代的(將'return permutations([1,2,3])'''返回給'return list(permutations([1,2,3]),我會更滿意。 ]))'不應該傳遞**和**(2)嵌套的期望值應該有正確的類型,這就是將其中一個元組更改爲不應該傳遞的列表(將'(2,3,1)'更改爲' [2,3,1]')。 – Tarrasch

+0

啊,很公平,我想我通常會避免測試類型,並嘗試測試接口,這樣可以在某些方面改變實現細節,同時繼續生成相同的數據/結果 – dbn

0

我不知道的任何標準方式Python程序員測試iterables,但你可以 簡單地套用您的maplist想法變成一個遞歸函數 爲嵌套結構的任何級別的工作。

def unroll(item): 
    if "__iter__" in dir(item): 
    return map(unroll, item) 
    else: 
    return item 

然後您的測試將實際工作。

def test_product_perms(): 
    got = unroll(product(...)) 
    expected = [[[1, 2], [3, 4]], [[1, 2], [4, 3]], ...] 
    assertEqual(got, expected) 

但是,您可以看到有一個缺陷。展開某些東西時,它總是會被轉換爲一個數組,但這對迭代器來說是可取的,但它也適用於元組。因此,我不得不手動將預期結果中的元組轉換爲列表。因此,如果輸出是列表或元組,則不能使用diffrentiate。

這種天真的方法的另一個問題是,通過測試並不意味着 該功能的工作。假設你檢查assertEqual(list(my_fun()), [1, 2, 3]),而你認爲​​它可能會返回一個迭代,當「列出」是 等於[1, 2, 3]。它可能是因爲你想要返回一個迭代,它可能已經返回了一個列表或一個元組!

1

你可以擴展你的建議,包括type(這可以讓你區分列表,元組等。),像這樣:

def unroll(item): 
    if "__iter__" in dir(item): 
    return map(unroll, item), type(item) 
    else: 
    return item, type(item) 

例如:

got = unroll(permutations([1,2])) 
([([(1, <type 'int'>), (2, <type 'int'>)], <type 'tuple'>), ([(2, <type 'int'>), (1, <type 'int'>)], <type 'tuple'>)], <type 'itertools.permutations'>) 
# note the final: <type 'itertools.permutations'> 
expected = [(1, 2), (2, 1)] 
assertEqual(x[0], unroll(expected)) # check underlying 
assertEqual(x[1], type(permutations([])) # check type 

有一點需要注意,type在區分對象例如<type 'classobj'> ...

+1

我寧願'isinstance()''以上類型()'。 –

+0

@AshwiniChaudhary我在想我留言/離開工作時,有一個更好的方法:) –

2

1.如果結果的順序並不重要

使用unittest.assertItemsEqual()。這測試項目是自我和參考,但忽略順序。這適用於您的示例嵌套深層示例。它也適用於我編制的2個深度示例。

2.如果結果的順序事項

我會建議不要永遠鑄造perm3()到列表中的結果。相反,直接在迭代時比較元素。這裏有一個測試函數可以用於你的例子。我把它添加到的unittest.TestCase生成一個子類:

def assertEqualIterables(self, itable1, itable2): 
    for ival1, ival2 in zip(itable1, itable2): 
     if "__iter__" in dir(ival1): 
      self.assertEqualIterables(ival1, ival2) 
     else: 
      self.assertEquals(ival1, ival2) 

這樣使用它:

def test_perm3(self): 
    reference = [((1, 2), (3, 4)), ((1, 2), (4, 3)), 
       ((2, 1), (3, 4)), ((2, 1), (4, 3)),] 

    self.assertEqualIterables(perm3(), reference) 
+0

有沒有人注意到assertItemsEqual在迭代器的幾個深層嵌套上工作?我實際上並沒有期待它的工作......我是否瘋了? – dbn