8

我在测试返回可迭代的 python 函数时遇到困难,例如产生的函数或仅返回可迭代的函数,例如return imap(f, some_iter)or return permutations([1,2,3])

因此,对于排列示例,我希望函数的输出为[(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), ...])

这很好用。但是如果我有嵌套的迭代呢?那是迭代产生迭代吗?喜欢说表情 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。

那么,python 测试社区是否有针对测试迭代的问题的解决方案呢?当然,有些可迭代对象不能以这种方式进行测试,比如如果你想要一个无限的生成器,但这个问题仍然应该足够普遍,以至于有人已经考虑过这个问题。

4

4 回答 4

4

我使用KennyTM 的 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)
于 2012-09-28T17:13:51.900 回答
2

1. 如果结果的顺序无关紧要

使用unittest.assertItemsEqual()。这将测试项目是否存在于 self 和 reference 中,但忽略顺序。这适用于您的示例一个嵌套的深层示例。它也适用于我编写的一个 2-deep 示例。

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)
于 2012-10-04T20:41:45.087 回答
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'>...

于 2012-09-28T17:02:59.350 回答
0

我不知道 python 程序员测试迭代的任何标准方式,但您可以简单地将您的想法应用map到适用list于任何嵌套级别的递归函数中。

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)

但是,如您所见,这有一个缺陷。当展开某些东西时,它总是会变成一个数组,这对于可迭代对象来说是可取的,但它也适用于元组。因此,我不得不手动将预期结果中的元组转换为列表。因此,您无法区分输出是列表还是元组。

这种幼稚方法的另一个问题是,通过测试并不意味着该功能有效。假设您检查assertEqual(list(my_fun()), [1, 2, 3]),而您认为它可能会返回一个迭代,当“列出”时等于[1, 2, 3]。可能是它没有返回您想要的可迭代对象,它也可能返回了列表或元组!

于 2012-09-28T16:21:54.860 回答