2012-09-28 14 views
8

ich Schwierigkeiten Testen Python Funktionen aufweisen, die, wie Funktionen eine iterable zurück, die Nachgeben oder Funktionen, die einfach eine iterable zurückkehren, wie return imap(f, some_iter) oder return permutations([1,2,3]).Testfunktionen iterable in Python Rückkehr

So mit dem Beispiel Permutationen erwarte ich die Ausgabe der Funktion [(1, 2, 3), (1, 3, 2), ...]. Also beginne ich meinen Code zu testen.

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), ...]) 

arbeiten Dies wird nicht, da perm3() ein iterable, kein Liste. So können wir dieses spezielle Beispiel beheben.

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

Und das funktioniert gut. Aber was, wenn ich iterable verschachtelt habe? Das ist iterable ergibt iterables? Wie sagen die Ausdrücke product(permutations([1, 2]), permutations([3, 4])). Nun ist dies wahrscheinlich nicht sinnvoll, aber es ist klar, dass es (einmal die Iteratoren entrollt) etwas wie [((1, 2), (3, 4)), ((1, 2), (4, 3)), ...]. Wir können jedoch nicht nur list um unser Ergebnis wickeln, da das nur iterable<blah> zu [iterable<blah>, iterable<blah>, ...] wird. Nun natürlich kann ich map(list, product(...)), aber dies funktioniert nur für einen Verschachtelungsebene von 2.

So hat die Python-Test-Community haben keine Lösung für die Probleme wenn Iterables testen? Natürlich können einige Iterables nicht auf diese Weise getestet werden, wie wenn Sie einen unendlichen Generator wollen, aber immer noch dieses Problem sollte genug sein, dass jemand darüber nachgedacht haben könnte darüber.

Antwort

4

Ich benutze 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

Ich werde das akzeptieren, weil es so gut wie die Antwort von dbw ist. Anders als meine Antwort können Sie 'Tupel' /' Listen' mischen. Diese Antwort ist auch nur kopierbar runableable. :) Aber! Ich wäre zufriedener, wenn es eine Antwort gäbe, bei der Sie (1) tatsächlich prüfen, ob sie eine iterierbare (Rückgabewerte ([1,2,3]) 'zurückgibt zu' Liste (Permutationen ([1,2,3 ])) 'sollte nicht übergeben ** und ** (2) der geschachtelte erwartete Wert sollte korrekte Typen haben.Das Ändern eines der Tupel zu einer Liste sollte nicht passieren (ändern Sie' (2, 3, 1) 'in' [2, 3, 1] '). – Tarrasch

+0

Ah, das ist gut genug, ich nehme an, dass ich Testtypen normalerweise vermeide und stattdessen versuche, Interfaces zu testen, damit die Implementierungsdetails sich ändern und die gleichen Daten/Ergebnisse erhalten bleiben – dbn

0

Ich weiß nicht, von jeder Standard-Weise Python-Programmierer Iterables testen, aber Sie einfach Ihre Vorstellung von map und list in eine rekursive Funktion arbeitet für jede Ebene des Verschachtelungsgrad gelten.

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

Dann wird Ihr Test tatsächlich funktionieren.

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

Allerdings gibt es einen Fehler mit diesem, wie Sie sehen können. Wenn man etwas abrollt, wird immer in ein Array umgewandelt, dies war für iterables wünschenswert, aber es gilt auch für die Tupel . Also musste ich die Tupel im erwarteten Ergebnis manuell in Listen umwandeln. Daher können Sie nicht differentiieren, wenn Ausgaben Listen oder Tupel sind.

Ein weiteres Problem mit diesem naiven Ansatz ist, dass ein bestandener Test nicht bedeutet, dass die Funktion funktioniert. Sagen Sie, dass Sie assertEqual(list(my_fun()), [1, 2, 3]) überprüfen, während Sie denken, dass es eine iterable zurückgeben kann, wenn "aufgeführt" gleich [1, 2, 3] ist. Es könnte sein, dass es keine iterable zurückgegeben hat, wie Sie wollten, es könnte auch eine Liste oder ein Tupel zurückgegeben haben!

1

Sie könnten Ihren Vorschlag erweitern, um type einzuschließen (das erlaubte Ihnen, zwischen Listen, Tupeln usw. zu unterscheiden).), Etwa so:

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

Zum Beispiel:

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 

.

Eine Sache zu erwähnen, type ist grob in der Unterscheidung zwischen Objekte z. <type 'classobj'> ...

+1

Ich würde es vorziehen 'isinstance()' 'über Art()'. –

+0

@AshwiniChaudhary Ich dachte, als ich postete/verließ die Arbeit, es gab einen besseren Weg :) –

2

1. Wenn die Reihenfolge der Ergebnisse nicht

Verwenden unittest.assertItemsEqual() egal. Dies testet, dass die Elemente sowohl in Selbst- als auch in Referenz vorhanden sind, ignoriert jedoch die Reihenfolge. Dies funktioniert in Ihrem Beispiel für ein verschachteltes Beispiel. Es funktioniert auch auf einem 2-tiefen Beispiel, das ich zusammengebraut habe.

2. Wenn die Reihenfolge der Ergebnisse von Bedeutung

Ich würde nie vorschlagen, die Ergebnisse der perm3 Gießen() auf eine Liste. Vergleichen Sie stattdessen die Elemente direkt beim Iterieren. Hier ist eine Testfunktion, die für Ihr Beispiel funktioniert. Ich habe es zu einer Unterklasse von 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) 

verwenden Sie es wie:

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

Hat jemand anderes bemerkt, dass assertItemsEqual an mehreren tiefen Nestern von Iteratoren arbeitet? Ich habe nicht wirklich erwartet, dass es funktioniert ... Bin ich verrückt? – dbn