2012-09-28 70 views
8

estoy teniendo dificultades funciones pitón de prueba que devuelven un iterable, como funciones que son rendimiento o funciones que simplemente devuelven un iterable, como return imap(f, some_iter) o return permutations([1,2,3]).Funciones de test que regresan iterable en Python

Así que con el ejemplo de las permutaciones, espero que la salida de la función sea [(1, 2, 3), (1, 3, 2), ...]. Entonces, empiezo a probar mi código.

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

Esto no funcionará, ya que es un perm3() iterable, no una lista . Entonces podemos arreglar este ejemplo en particular.

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

Y esto funciona bien. Pero, ¿y si he anidado iterables? ¿Eso es iterables produciendo iterables? Como decir las expresiones product(permutations([1, 2]), permutations([3, 4])). Ahora bien, esto es probablemente no sea útil, pero está claro que será (una vez que se desenrollen los iteradores ) algo así como [((1, 2), (3, 4)), ((1, 2), (4, 3)), ...]. Sin embargo, no podemos simplemente ajustar list alrededor de nuestro resultado, ya que solo convertirá iterable<blah> en [iterable<blah>, iterable<blah>, ...]. Así por supuesto que puede hacer map(list, product(...)), pero esto sólo funciona para un nivel de anidamiento 2.

Así que, ¿la comunidad de pruebas de Python tienen ninguna solución para los problemas al probar iterables? Naturalmente, algunos iterables no pueden probarse de esta manera, como si usted quiere un generador infinito, pero todavía este problema debería ser lo suficientemente común como para que alguien haya pensado acerca de esto.

Respuesta

4

utilizo 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

Aceptaré esto porque es tan bueno como la respuesta de dbw. A diferencia de mi respuesta, puedes mezclar 'tuples' /' lists'. Esta respuesta también es solo copiar y pegar ejecutable. :) ¡Pero! Estaría más satisfecho si hubiera una respuesta donde (1) realmente compruebe que devuelve un iterable (cambie 'permutaciones de retorno ([1,2,3])' a 'lista de retorno (permutaciones ([1,2,3 ])) 'no debe pasar ** y ** (2) el valor esperado anidado debe tener tipos correctos. Eso es cambiar una de las tuplas a una lista no debe pasar (cambiar' (2, 3, 1) 'a' [2, 3, 1] '). – Tarrasch

+0

Ah. Es suficiente. Supongo que generalmente evito los tipos de prueba y trato de probar las interfaces en su lugar. Esto permite que los detalles de implementación cambien de alguna manera, mientras se siguen produciendo los mismos datos/resultados. . – dbn

0

no sé de cualquier forma estándar programadores de Python prueba iterables, pero puede simplemente aplicar su idea de map y list en una función recursiva de trabajo para cualquier nivel de anidamiento.

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

Entonces su prueba realmente funcionará.

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

Sin embargo, hay un defecto en esto, como puede ver. Al desenrollar algo, siempre se convertirá en una matriz, esto era deseable para los iterables, pero también se aplica a las tuplas. Entonces tuve que convertir manualmente las tuplas en el resultado esperado a listas. Por lo tanto, no se puede diferenciar si las salidas son listas o tuplas.

Otro problema con este enfoque ingenuo es que una prueba de aprobación no significa que la función funcione. Digamos que marque assertEqual(list(my_fun()), [1, 2, 3]), mientras que usted piensa que podría devolver un iterable que cuando está "en la lista" es igual a [1, 2, 3]. ¡Puede ser que no haya devuelto un iterable como lo deseaba, podría haber devuelto una lista o una tupla también!

1

Puede ampliar su sugerencia para incluir type (que le permitía distinguir entre listas, tuplas, etc.), Así:

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

Por ejemplo:

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 

.

Una cosa para mencionar, type es gruesa para distinguir entre objetos por ej. <type 'classobj'> ...

+1

preferiría 'isinstance()' 'tipo sobre()'. –

+0

@AshwiniChaudhary Estaba pensando que cuando publiqué/dejé el trabajo, había una mejor manera :) –

2

1. Si el orden de los resultados no importa

Uso unittest.assertItemsEqual(). Esto prueba que los elementos están presentes tanto en sí mismo como en la referencia, pero ignora el orden. Esto funciona en tu ejemplo, un ejemplo profundo anidado. También funciona en un ejemplo de 2 profundidades que inventé.

2. Si el orden de los resultados importa

Yo sugeriría que no siempre los resultados de fundición perm3() a una lista. En su lugar, compare los elementos directamente a medida que itera. Aquí hay una función de prueba que funcionará para su ejemplo. He añadido a una subclase de 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) 

usarlo como:

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

¿Alguien más ha notado que assertItemsEqual está trabajando en varios nidos profundos de iteradores? En realidad no esperaba que funcionara ... ¿Estoy loco? – dbn

Cuestiones relacionadas