2012-09-28 79 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