2012-07-08 11 views
5

Estoy probando clases en Python usando unittest. Según tengo entendido, unittest llama a la función setUp antes de cada prueba para que el estado de los objetos de prueba de la unidad sea el mismo y el orden en que se ejecuten las pruebas no importaría.Objetos de prueba de unidad en Python: el objeto no está escrito en la configuración

Ahora tengo esta clase Estoy probando ...

#! usr/bin/python2 

class SpamTest(object): 

    def __init__(self, numlist = []): 
     self.__numlist = numlist 

    @property 
    def numlist(self): 
     return self.__numlist 

    @numlist.setter 
    def numlist(self, numlist): 
     self.__numlist = numlist 

    def add_num(self, num): 
     self.__numlist.append(num) 

    def incr(self, delta): 
     self.numlist = map(lambda x: x + 1, self.numlist) 

    def __eq__(self, st2): 
     i = 0 
     limit = len(self.numlist) 

     if limit != len(st2.numlist): 
      return False 

     while i < limit: 
      if self.numlist[i] != st2.numlist[i]: 
       return False 

      i += 1 

     return True 

con las siguientes pruebas unitarias ...

#! usr/bin/python2 

from test import SpamTest 

import unittest 

class Spammer(unittest.TestCase): 

    def setUp(self): 
     self.st = SpamTest() 
     #self.st.numlist = [] <--TAKE NOTE OF ME! 
     self.st.add_num(1) 
     self.st.add_num(2) 
     self.st.add_num(3) 
     self.st.add_num(4) 

    def test_translate(self): 
     eggs = SpamTest([2, 3, 4, 5]) 
     self.st.incr(1) 
     self.assertTrue(self.st.__eq__(eggs)) 

    def test_set(self): 
     nl = [1, 4, 1, 5, 9] 
     self.st.numlist = nl 
     self.assertEqual(self.st.numlist, nl) 

if __name__ == "__main__": 
    tests = unittest.TestLoader().loadTestsFromTestCase(Spammer) 
    unittest.TextTestRunner(verbosity = 2).run(tests) 

Esta prueba falla por test_translate.

que puedo hacer dos cosas para que las pruebas tienen éxito:

(1) Elimine la segunda línea en la función de configuración. O bien,

(2) Cambie los nombres de las pruebas de manera que ocurra primero translate. Me di cuenta de que unittest ejecuta las pruebas en orden alfabético. Cambiar translate a, digamos, atranslate para que se ejecute primero hace que todas las pruebas sean exitosas.

Para (1), no puedo imaginar cómo esto afecta las pruebas ya que en la primera línea de setUp, creamos un nuevo objeto para self.st. En cuanto a (2), mi queja es similar ya que, oye, en setUp asigno un nuevo objeto a self.st así que cualquier cosa que haga a self.st en no debe afectar el resultado de test_translate.

Entonces, ¿qué es lo que me falta aquí?

Respuesta

10

Sin estudiar el detalle de su solución, debe leer el Default Parameter Values in Python de Fredrik Lundh.

Es probable que explique su problema con lista vacía como argumento predeterminado. La razón es que la lista está vacía solo por primera vez, a menos que la vacíe explícitamente más tarde.La lista predeterminada de initially empty es la única instancia del tipo de lista que se reutiliza cuando no se pasa ningún argumento explícito.

Es una buena idea leer el artículo anterior para corregir su pensamiento sobre los argumentos predeterminados. Las razones son lógicas, pero pueden ser inesperadas.

El arreglo generalmente recomendada es utilizar None como el valor por defecto de la __init__ y establecer la lista vacía dentro del cuerpo si el argumento no se pasa, de esta manera:

class SpamTest(object): 

    def __init__(self, numlist=None): 
     if numlist is None: 
      numlist = []   # this is the new instance -- the empty list 
     self.__numlist = numlist 
+0

Entonces ... educarme un poco más. ¿Cuál es la diferencia entre 'is None' y' == None'? Siempre utilicé 'is None'; no sabía que '== None' funciona. – skytreader

+1

@skytreader: El operador 'is' prueba la identidad del objeto. El valor 'None' está representado por la instancia única de la clase NoneType. Tener el valor 'Ninguno' significa que está compartiendo la referencia al objeto idéntico. La 'numlist es Ninguna 'significa que está probando si el objeto idéntico es compartido. El operador '==' es más complicado. Si la 'numlist' fuera la instancia (referencia a) de una clase que define su propio método' .__ eq__', ''==' podría producir un valor booleano inesperado. Sin embargo, es casi lo mismo en casos simples. – pepr

4

Esto se debe a la forma en que los parámetros predeterminados se comportan en Python al usar objetos Mutable como listas: Default Parameter Values in Python.

En la línea:

def __init__(self, numlist = []): 

El parámetro por defecto para numlist sólo se evalúa una vez para que sólo tenga una instancia de la lista que se comparte a través de toda instancia de la clase SpamTest.

Por lo tanto, aunque se requiere la prueba setUp para cada prueba, nunca crea una nueva lista vacía, y las pruebas que funcionan en esa instancia lista terminan pisándose los dedos del pie.

La solución es tener algo como esto en su lugar, utilizando un objeto no mutable como None:

def __init__(self, numlist = None): 
    if numlist is None: 
     numlist = [] 
    self.__numlist = numlist 

La razón funciona al establecer la propiedad es que usted proporciona una nueva lista vacía allí, reemplazando la lista creada en el constructor.

+0

Oh, mierda. Mi +1> :) Parece que copié/pegué su solución. Pero en realidad es solo la coincidencia. Probablemente solo 'numlist es None' sería mejor. – pepr

+0

@pepr no hay problemas +1 de mi parte para usted también por buena forma, y ​​la respuesta correcta también :) El artículo de Fredrik Lundh realmente es genial, por lo que no sorprende que los dos lo hayamos mencionado. –

Cuestiones relacionadas