Prueba unitaria y pruebas abstractas

En lugar de unirse


Unittest es probablemente el marco de escritura de prueba más famoso en Python. Es muy fácil de aprender y fácil de comenzar a usar en su proyecto. Pero nada es perfecto. En esta publicación, quiero hablar sobre una característica que personalmente (creo que no una) me falta en unittest.

Un poco sobre las pruebas unitarias


Antes de discutir (y condenar) el marco de prueba, considero necesario hablar un poco sobre las pruebas en general. Cuando escuché por primera vez la frase "prueba de unidad", pensé que era responsabilidad de algún servicio de control de calidad que verificara el cumplimiento de los requisitos por parte de los módulos de software. Imagine mi sorpresa cuando descubrí que estas mismas pruebas deberían ser escritas por programadores. Al principio no escribí pruebas ... en absoluto. Nada puede reemplazar esas sensaciones cuando te levantas por la mañana y lees al usuario “El programa no funciona. Hay que hacer algo urgentemente ". Al principio me pareció que este era un proceso completamente normal, hasta que escribí la primera prueba unitaria, que encontró un error. Las pruebas unitarias generalmente parecen ser inútiles exactamente hasta que detectan problemas. Ahora no puedo comprometer una nueva función sin escribir una prueba en ella.
Pero las pruebas no solo se utilizan para verificar que el código esté escrito correctamente. Aquí hay una lista de funciones que, en mi opinión, realizan pruebas:

  • Detección de errores en el código del programa.
  • Dar a los programadores la confianza de que su código funciona
  • Corolario del párrafo anterior: la capacidad de modificar de forma segura el programa
  • Pruebas: un tipo de documentación que describe más correctamente el comportamiento del sistema.

En cierto sentido, las pruebas repiten la estructura del programa. ¿Los principios de los programas de construcción son aplicables a las pruebas? Creo que sí, este es el mismo programa, aunque está probando otro.

Descripción del problema


En algún momento, tuve la idea de escribir una prueba abstracta. ¿Qué quiero decir con eso? Esta es una prueba que no se ejecuta sola, pero declara métodos que dependen de los parámetros definidos en los herederos. Y luego descubrí que no puedo hacer esto humanamente en unittest. Aquí hay un ejemplo:

class SerializerChecker(TestCase): model = None serializer = None def test_fields_creation(self): props = TestObjectFactory.get_properties_for_model(self.model) obj = TestObjectFactory.create_test_object_for_model(self.model) serialized = self.serializer(obj) self.check_dict(serialized.data, props) 

Creo que, incluso sin conocer la implementación de TestObjectFactory y el método check_dict, está claro que props es un diccionario de propiedades para un objeto, obj es un objeto para el que estamos verificando el serializador. check_dict verifica recursivamente los diccionarios para una coincidencia. Creo que muchas personas familiarizadas con unittest dirán de inmediato que esta prueba no cumple con mi definición de resumen. Por qué Debido a que el método test_fields_creation se ejecutará desde esta clase, que absolutamente no necesitamos. Después de buscar información, llegué a la conclusión de que la opción más adecuada no es heredar SerializerChecker de TestCase, sino implementar de alguna manera a los herederos:

 class VehicleSerializerTest(SerializerChecker, RecursiveTestCase): model = Vehicle serializer = VehicleSerialize 

RecursiveTestCase es un descendiente de TestCase que implementa el método check_dict.

Esta solución es fea a la vez desde varias posiciones:

  • En la clase SerializerChecker, necesitamos saber que el hijo debe heredar de TestCase. Esta dependencia puede causar problemas a quienes no estén familiarizados con este código.
  • El entorno de desarrollo tercamente cree que estoy equivocado, ya que SerializerChecker no tiene un método check_dict

imagen

Error que arroja el entorno de desarrollo

Puede parecer que simplemente puede agregar un código auxiliar para check_dict y todos los problemas se han resuelto:

 class SerializerChecker: model = None serializer = None def check_dict(self, data, props): raise NotImplementedError def test_fields_creation(self): props = TestObjectFactory.get_properties_for_model(self.model) obj = TestObjectFactory.create_test_object_for_model(self.model) serialized = self.serializer(obj) self.check_dict(serialized.data, props) 

Pero esta no es una solución completa al problema:

  • De hecho, creamos una interfaz que implementa no un descendiente de esta clase, sino RecursiveTestCase, que crea preguntas razonables para la arquitectura.
  • TestCase tiene muchos métodos de aserción *. ¿Realmente necesitamos escribir un trozo para cada uno usado? ¿Todavía parece una buena solución?

Para resumir


Unittest no proporciona la capacidad sensata de "desconectar" una clase heredada de TestCase. Estaría muy contento si dicha función se agregara al marco. ¿Cómo resuelves este problema?

Source: https://habr.com/ru/post/447438/


All Articles