2010-06-22 17 views
5

Me pregunto qué técnicas utilizan las personas para simplificar el "tamaño" del código utilizado para las pruebas unitarias. Por ejemplo, estaba tratando de ordenar un objeto de la clase y probar el objeto mariscal (pero esto supone que el oficial está trabajando correctamente).¿Hay un estilo mínimo para unittest en Python?

en cuenta la clase

import unittest 
class Nums(object): 
    def __init__(self, n1_, n2_, n3_): 
     self.n1, self.n2, self.n3 = n1_, n2_, n3_ 
def marshal(self): 
    return "n1 %g, n2 %g, n3 %g"%(self.n1,self.n2,self.n3) 

y luego el cálculo de referencias basado, lista basada, y pruebas normales

class NumsTests(unittest.TestCase): 
    def setUp(self): 
     self.nu = Nums(10,20,30) 
    def test_init1(self): 
     self.assertEquals(self.nu.marshal(),"n1 %g, n2 %g, n3 %g"%(10,20,30)) 
    def test_init2(self): 
     self.assertEquals([self.nu.n1,self.nu.n2,self.nu.n3],[10,21,31]) 
    def test_init3(self): 
     self.assertEquals(self.nu.n1,10) 
     self.assertEquals(self.nu.n2,21) 
     self.assertEquals(self.nu.n3,31) 

que dan los siguientes errores (ya que, 20! = 21 y 30! = 31 , mi prueba tiene una inicialización incorrecta o las condiciones de prueba son incorrectas)

AssertionError: 'n1 10, n2 20, n3 30' != 'n1 10, n2 21, n3 31' 

AssertionError: [10, 20, 30] != [10, 21, 31] 

AssertionError: 20 != 21 

La primera y segunda er Los mensajes ror son difíciles de entender (ya que tiene que analizar la cadena o la lista). Sin embargo, la tercera técnica explota rápidamente en la cantidad de código utilizado para probar objetos complejos.

¿Existe una manera mejor de simplificar las pruebas unitarias sin crear mensajes de error difíciles? Y, sin depender de la veracidad de una función de Mariscal?

[cambió test_marshal-marshal]

+0

No debe tener ningún código de prueba en su sistema bajo prueba (en este caso, la clase 'Nums'). – Skilldrick

+0

@Skilldrick No sé qué significa "código de prueba en el sistema bajo prueba". –

+1

el código que prueba 'Nums' debe estar en un archivo separado de la clase' Nums' en sí. Así que mueva 'test_marshal' a otro archivo (es decir, su archivo de prueba). –

Respuesta

2

Para la inicialización de la prueba, recomiendo no realizando la prueba llamando a la función marshal(). No solo tendrá que analizar qué inicializador falló, sino también determinar si es la inicialización la que está fallando o la función Marshal. El "estilo mínimo" para las pruebas unitarias que recomendaría es reducir el enfoque de lo que está probando en cualquier punto de prueba tanto como sea factible.

Si realmente tenía que probar la inicialización de un montón de campos, puede ser que refactorizar la misma manera como Eli:

class MyTestCase(unittest.TestCase): 
    def assertFieldsEqual(self, obj, expectedFieldValDict): 
     for fieldName, fieldVal in expectedFieldValDict.items(): 
      self.assertFieldEqual(obj, fieldName, fieldVal) 
    def assertFieldEqual(self, obj, fieldName, expectedFieldVal): 
     actualFieldVal = getattr(obj, fieldName) 
     if expectedFieldVal != actualFieldVal: 
      msg = "Field %r doesn't match: expected %r, actual %r" % \ 
       (fieldName, expectedFieldVal, actualFieldVal) 
      self.fail(msg) 

class NumsTests(MyTestCase): 
    def setUp(self): 
     self.initFields = {'n1': 10, 'n2': 20, 'n3': 30} 
     self.nums = Nums(**initFields) 
    def test_init(self): 
     self.assertFieldsEqual(self.nums, self.initFields) 

"Por Dios," Puedo oírle decir, "que es una ¡Mucho código! " Bueno, sí, pero las diferencias son:

  • assertFieldsEqual y assertFieldEqual son funciones reutilizables que han sido resumidas a una clase de casos de prueba común, que sus otros casos de prueba pueden reutilizar.
  • El mensaje de falla describe exactamente lo que está sucediendo.

Cuando llega el momento de poner a prueba su función mariscal, simplemente puede hacer esto:

def test_marshal(self): 
    expected = '...' 
    self.assertEqual(expected, self.nums.marshal()) 

Cuando se comparan series, sin embargo, yo prefiero un método que me dice exactamente donde divergen las cuerdas. Para las cadenas de varias líneas, ahora hay un método para eso en Python 2.7, pero he pasado o reducido mis propios métodos para este propósito en el pasado.

3

que repetir los comentarios anteriores que no se debe tener métodos de prueba en la clase real que se está probando. Las funciones como test_marshal deben colocarse en otro lugar (suponiendo que existan para pruebas y no para uso general), generalmente en los archivos de prueba de la unidad. Sin embargo, el establecimiento de que lado por el momento, me gustaría hacer algo como esto

import unittest 

class Nums(object): 
    FORMAT = "n1 %g, n2 %g, n3 %g" # make this a variable for easy testing 

    def __init__(self, n1, n2, n3): 
     self.n1, self.n2, self.n3 = n1, n2, n3 # no underscores necessary 

    def test_marshal(self): 
     return self.FORMAT % (self.n1, self.n2, self.n3) 


class NumsTests(unittest.TestCase): 
    def setUp(self): 
     self.nums = [10, 20, 30] # make a param list variable to avoid duplication 
     self.nu = Nums(*self.nums) # Python "apply" syntax 
     self.nu_nums = [self.nu.n1, self.nu.n2, self.nu.n3] # we'll use this repeatedly 

    def test_init1(self): 
     self.assertEquals(self.nu.test_marshal(), self.nu.FORMAT % self.nums) 

    def test_init2(self): 
     self.assertEquals(self.nums, self.nu_nums) 

    def test_init3(self): 
     for reference, test in zip(self.nums, self.nu_nums): 
      self.assertEquals(reference, test) 

Ver http://docs.python.org/library/functions.html#apply para una explicación de la sintaxis de aplicación utilizado anteriormente.

Al poner las cosas que está probando en variables, puede evitar la duplicación de código, que parece ser su principal preocupación.

En cuanto a los mensajes de error que son confusos, supongo que depende de la cantidad de detalles que sienta que necesita. Personalmente, el hecho de que las pruebas de mi unidad me den la línea de código y los valores que se esperaban y no estaban presentes, hace que quede bastante claro qué fue lo que salió mal. Sin embargo, si realmente quería algo que le ha dicho específicamente qué campo era incorrecto, en lugar de que sólo los valores que no coinciden y que querían evitar la duplicación de código, podría escribir algo como esto:

class NumsTests(unittest.TestCase): 
    def setUp(self): 
     self.nums = {"n1": 10, "n2": 20, "n3": 30} # use a dict, not a list 
     self.nu = Nums(**self.nums)     # same Python "apply" syntax 

    # test_init1 and test_init2 omitted for space 

    def test_init3(self): 
     for attr,val in self.nums.items(): 
      self.assertEqual([attr, val], [attr, getattr(self.nu, val)]) 

Si alguna vez tenía valores no coinciden, ahora se obtendría errores que se parecen a

AssertionError: ["n1", 10] != ["n1", 11] 

y por lo tanto desea saber de un vistazo lo que no coincide con el campo, en lugar de tener que razonar en base a lo los valores eran Este enfoque aún conserva el problema de expansión del código, ya que test_init3 permanecerá del mismo tamaño sin importar cuántos parámetros agregue a su clase Nums.

Tenga en cuenta que este uso de getattr requiere que sus parámetros __init__ tengan el mismo nombre que los campos en la clase num, p. deben llamarse n1 en lugar de n1_, etc. Un enfoque alternativo sería usar el __dict__ attribute, as described here.

+1

¿Por qué no cambiar el nombre test_marshal() a __str __()? O tal vez cambiarle el nombre a __repr __() y establecer el formato a "Nums (n1 _ =% g, n2 _ =% g, n3 _ =% g)"? – Oddthinking

+0

De corazón segundo esas ideas. –

Cuestiones relacionadas