En la programación estructurada, solo nos preocupamos por llegar de un punto A a un punto B. O en otras palabras, dada una entrada, deseamos obtener una salida específica sin importar el proceso para obtenerla. No importa cuantas veces repitamos el proceso, obtendremos la misma salida para la misma entrada.

Puede ser una caja negra, sin problemas. Fuente: https://previews.123rf.com/images/onlyblack/onlyblack1811/onlyblack181100119/113271703-draws-a-line-from-a-to-b-male-hand-writing-text-on-white-background-.jpg
Cuando aprendí que el paradigma orientado a objetos, se trataba de generar objetos que se poseyeran parámetros que los definieran, métodos que delinearan su comportamiento y que estos serían necesarios para comunicarse entre objetos a través de mensajes, se me figuró que este paradigma era como tener una mascota.
Lo importante de una mascota no es solo las entradas y salidas ( en una mascota son triviales dichas entradas y salidas, comida y residuos). Sino que también es importante el estado y comportamiento de los objetos, como su interacción con los otros para que el sistema funcione. ( Si a las mascotas las quisieran tan solo por alimentarlas y limpiarles sus residuos, la gente tendría plantas).

Fuente: https://www.65ymas.com/uploads/s1/65/73/5/bigstock-dog-beagle-having-fun-running-294325987-1.jpeg
De cierta forma, podemos decir que el código generado a través del paradigma orientado a objetos será un código vivo.
Esto nos da ventajas al diseñar nuestros sistemas. A fin de cuentas, el proceso de diseñar entidades y sus interacciones es un proceso ligado íntimamente a la estructura del lenguaje.
Sin embargo, para evaluar una entidad orientada a objetos, es necesario observar el cambio de estado, comportamiento y salidas de los distintos objetos.
En el caso de una mascota, poco caso tendría evaluar si, dada cierta cantidad de comida, obtenemos cierta cantidad de residuos. El análisis del estado de la mascota sería muy limitado y no representa a una mascota ( con una mascota jugamos, la sacamos a pasear, hace travesuras y nos apapacha).
Por lo tanto, se puede observar que, de buenas a primeras, la evaluación de nuestros sistemas orientados a objetos serán un poco más complicados de evaluar.
Implicaciones en el test de aplicaciones orientadas a objetos
En un sistema orientado a objetos, para poder evaluarlo de forma total, tenemos que considerar:
- Conductas dependientes del estado
- Encapsulación
- Herencia
- Polimorfismo y enlaces dinámicos
- Clases abstractas y genéricas
- Manejo de excepciones
Ya no solo debemos tomar en cuenta entradas y salidas . . .
Por ende, la cantidad de tests para evaluar sistemas cambia considerablemente.
El programa OO debe probarse en diferentes niveles para descubrir todos los errores:
A nivel algorítmico, cada módulo (o método) de cada clase en el programa debe probarse de forma aislada.
Como las clases forman la unidad principal del programa orientado a objetos, la prueba de clases es la principal preocupación al probar un programa OO. A nivel de clase, cada clase debe ser probada como una entidad individual.
Después de realizar la prueba a nivel de clase, se deben realizar pruebas a nivel de clúster. Como las clases se colaboran (o integran) para formar un pequeño subsistema (también conocido como clúster), es necesario probar cada clúster individualmente.
En este nivel, la atención se centra en probar los componentes que se ejecutan simultáneamente, así como en la interacción entre clases. Por lo tanto, las pruebas a este nivel pueden verse como pruebas de integración donde las unidades a integrar son clases. Una vez que se prueban todos los clústeres en el sistema, comienza la prueba a nivel del sistema. En este nivel, se prueba la interacción entre grupos.
Test basados en el estado
Los objetos se representan con máquinas de estados, un estado es el conjunto de valores dados para los atributos de un objeto.
Por lo tanto, los test basados en el estado del objeto inspeccionan el estado interior de un objeto, a manera de caja blanca.
Test basado en una máquina de estados finitos
Estos test evalúan el conjunto de estados del objeto y sus transiciones. Se debe tomar en cuenta la llegada a todos los estados, todas las transiciones entre estados, incluso aquellas transiciones no permitidas. Para esto, se debe tener conocimiento profundo de los estados y transiciones de sistema.
Test para evaluación de relaciones de herencia
Uno de los mayores errores es pensar que si reutilizamos código proveniente de una superclase, no sería necesario testear ese código en el contexto de la clase hija.
De hecho, por utilizar mismo código en distintos contextos es que cobra más fuerza la necesidad de testear de nuevo dicho código. Recordemos que, al aplicar conceptos de herencia, aumentan los problemas como:
- Inicialización incorrecta de atributos de superclase por la subclase
- Faltan métodos sobreescritos
- Acceso directo a los campos de superclase desde la subclase
- Una subclase viola un invariante de la superclase, o crea un estado no válido
- y más . . .
Para cubrir todos los escenarios generados posibles en el testeo de clases generadas debemos tomar en cuenta que los métodos heredados se deben volver a probar en el contexto de una subclase. Además, si agregamos o cambiamos una subclase, necesitamos volver a probar todos los métodos heredados de una superclase en el contexto de la subclase nueva / modificada.
Las pruebas son una actividad continua durante el desarrollo de software. En los sistemas orientados a objetos, las pruebas abarcan tres niveles, a saber, pruebas unitarias, pruebas de subsistemas y pruebas de sistemas.
Examen de la unidad
En las pruebas unitarias, se evalúan las clases individuales. Se ve si los atributos de clase se implementan según el diseño y si los métodos y las interfaces están libres de errores. Las pruebas unitarias son responsabilidad del ingeniero de aplicaciones que implementa la estructura.
Prueba de subsistema
Esto implica probar un módulo particular o un subsistema y es responsabilidad del líder del subsistema. Implica probar las asociaciones dentro del subsistema, así como la interacción del subsistema con el exterior. Las pruebas del subsistema se pueden usar como pruebas de regresión para cada versión recién lanzada del subsistema.
Prueba de sistema
La prueba del sistema implica probar el sistema en su conjunto y es responsabilidad del equipo de garantía de calidad. El equipo a menudo usa pruebas de sistema como pruebas de regresión cuando ensambla nuevas versiones.
Testeo de excepciones
Para cubrir los escenarios donde se ejecuten procesos causados por el manejo de excepciones, es necesario inyectar en el sistema valores de entrada erroneos los cuales se espere dicho comportamiento anómalo. En estos casos se testea que efectivamente la excepción adecuada sea lanzada o cachada.
Testeo de interacciones
Por lo general, existe la idea errónea de que si las clases individuales están bien diseñadas y han demostrado que funcionan de manera aislada, entonces no hay necesidad de probar las interacciones entre dos o más clases cuando están integradas. Sin embargo, esto no es cierto porque a veces puede haber errores, que solo pueden detectarse mediante la integración de clases. Además, es posible que si una clase no contiene un error, otra clase pueda usarla de manera incorrecta, lo que lleva a un fallo del sistema.
Para observar el conjunto de interacciones entre clases, se puede recurir a un diagrama de secuencia. Este muestra la cascada de mensajes entre un conjunto de objetos
Puede haber varios diagramas que muestran diferentes variaciones de la interacción.
Los test deben cubrir todos los mensajes posibles y condiciones, tanto escenarios esperados como alternativos.
Quizá sea mi estado anímico, pero para mí, este post ha sido un conjunto de ideas voladoras que dificilmente se conectan entre sí. En realidad es un intento de mi parte de darle orden a las ideas que hallaba por ahí en la web. Por ejemplo, para hallar ideas condensadas para el testeo de aplicaciones los encuentro aqui.
Por otro lado, me abrió el panorama la lectura de este pequeño blog.
Como cierre puedo decir: para código vivo, no es suficiente el testeo estático.