Guía Básica Para Probar Códigos Complejos

Guía Básica Para Probar Códigos Complejos
      El mundo de la programación es divertido cuando se está creando y unificando el código para obtener un sistema, sin embargo al momento de probarlo es donde se necesitan las mejores herramientas para que dicho sistema pueda ser utilizado posteriormente. Bajo una prueba ineficiente, ponemos en riesgo horas de esfuerzo. Por ello, he escrito una pequeña guía básica de conceptos necesarios para probar códigos complejos, los cuales de la mano de Andrey Shalitkin (freelancer ingeniero de software) te aportarán las herramientas necesarias, para que testees tu código como los expertos.


  • Test Driven (TDD): el propósito es es lograr un código limpio que funcione. La idea es que los requisitos sean traducidos a pruebas, de este modo, cuando las pruebas pasen se garantizará que el software cumple con los requisitos que se han establecido. Es una práctica de ingeniería de software. La princiapal ventaja es evitar escribir código innecesario. Se busca escribir el mínimo código posible, y si el código pasa una prueba aunque sepamos que es incorrecto nos provee una idea de que tenemos que modificar nuestra lista de requisitos agregando uno nuevo. La generación de pruebas para cada funcionalidad hace que el programador confíe en el código escrito. Esto permite hacer modificaciones profundas del código, puesto que sabemos que si luego logramos hacer pasar todas las pruebas tendremos un código que funcione correctamente. A su vez, requiere que el analista primero haga fallar los casos de prueba. El objetivo es asegurarse de que los casos de prueba realmente funcionen y puedan recoger un error.
  • Testeos: son las investigaciones empíricas y técnicas cuyo objetivo es proporcionar información objetiva e independiente sobre la calidad del producto a la parte interesada. Es una fase del proceso de control de calidad.
  • Testeos de Unidad: es una forma de comprobar el correcto funcionamiento de una unidad de código. Por ejemplo en diseño estructurado o en diseño funcional una función o un procedimiento, en diseño orientado a objetos una clase. Esto sirve para asegurar que cada unidad funcione correctamente y eficientemente por separado. Además de verificar que el código hace lo que tiene que hacer, verificamos que sea correcto el nombre, los nombres y tipos de los parámetros, el tipo de lo que se devuelve, que si el estado inicial es válido entonces el estado final es válido. El objetivo de las pruebas unitarias es aislar cada parte del programa y mostrar que las partes individuales son correctas.
  • Refactorización: consiste en tomar código ya existente y mejorarlo. De esta manera el código será más legible, podrá ampliarse con nuevas características, favoreciendo así su crecimiento, localizar y arreglar errores en él será más fácil. Es una técnica de la ingeniería de software para reestructurar un código fuente, alterando su estructura interna sin cambiar su comportamiento externo. Los desarrolladores alternan la inserción de nuevas funcionalidades y casos de prueba con la refactorización del código para mejorar su consistencia interna y su claridad. Los tests aseguran que la refactorización no cambia el comportamiento del código.
  • Acoplamiento Aferente: se produce cuando otro paquete hace uso de atributos y/o métodos de clases de dicha clase o hereda de alguna de ellas. Por tanto el cálculo del acoplamiento aferente se obtiene mediante la suma de clases de otros paquetes que cumplen las características indicadas. Un alto acoplamiento aferente hace referencia a un paquete con un alto grado de responsabilidad. La responsabilidad y la estabilidad son dos conceptos que suelen ir cogidos de la mano, precisamente porque al existir un elevado número de clases que dependen de clases del paquete, se tiene que ser necesariamente más prudente a la hora de realizar modificaciones en clases del paquete por los posibles efectos colaterales que se pueden producir. No obstante la estabilidad de un paquete no es función exclusivamente del acoplamiento aferente. 
  • Acoplamiento Eferente: se produce cuanto dicha clase hace uso de atributos y/o métodos de clases de otro paquete o hereda de clases de dicho paquete. Se obtiene como la suma de clases de otros paquetes de los cuales dependen clases del paquete con el que se está trabajando. Un alto acoplamiento eferente hace referencia a un paquete con un alto grado de dependencia. La dependencia y la inestabilidad son también dos conceptos íntimamente relacionados, ya que el funcionamiento de las clases del paquete dependen del comportamiento de clases externas, lo que las hace susceptibles de efectos colatarales en las modificaciones de las mismas y por tanto su funcionamiento entraña una mayor incertidumbre.


  • Complejidad Ciclomática: es una métrica de calidad software basada en el cálculo del número de caminos independientes que tiene nuestro código. Permite tomar la temperatura de nuestro código respecto su nivel de mantenibilidad, la probabilidad de incluir fallos, o el esfuerzo necesario para poder probar todos sus caminos. Cuanto más compleja sea la lógica de un código, más difícil será de entender, mantener y probar.
  • Función Genética: consiste en una función matemática o una rutina de software que toma como entradas a los ejemplares y retorna como salidas cuales de ellos deben generar descendencia para la nueva generación.
  • Función Fitness: no es más que la función objetivo de nuestro problema de optimización. El algoritmo genético únicamente maximiza, pero la minimización puede realizarse fácilmente utilizando el recíproco de la función maximizante (debe cuidarse, por supuesto, que el recíproco de la función no genere una división por cero). Una característica que debe tener esta función es que tiene ser capaz de "castigar" a las malas soluciones, y de "premiar" a las buenas, de forma que sean estas últimas las que se propaguen con mayor rapidez. 
 Algunas herramientas web gratuitas para probar códigos complejos son:

  •  CiVis: es un software que se encarga de las métricas para códigos java, así como también provee un entorno de visualización y análisis para el código en cuestión, mediante tablas y estadisticas.
  • SonarQube: es una herramienta multi lenguaje, que involucra los mas populares, como C++, C, Java, JS, COBOL; VB.Net, Python, entre otros. Se encarga de detectar errores lógicos, bugs, null pointers, y provee un estudio del código, para señalar los puntos donde se debe limpiar, para optimizarlo apropiadamente.
  • ckjm: el programa se encarga de calcular las métricas de la programación orientada a objetos basados en Chidamber y Kemerer para códigos java. Calcula para cada clase el acopplamiento aferente, el número de métodos públicos, las respuestas por una clase, número de hijos, la profundidad del árbol de iterancias y el peso de los métodos por clase.

      Ahora veamos un ejemplo de la aplicación de nuestra Guía Básica para Probar Códigos complejos, expuestos por el autor Andrey Shalitkin en su artículo "Escribe Testeos que Importen: Aborda el Código Mas Complejo Primero" el cual cubrirá toda la parte matemática:



El problema principal aquí es que tenemos dos criterios – CA y complejidad Ciclomática – por esto necesitamos combinarlas y convertirlas en un solo valor escalar. Si tuviéramos una tarea un poco diferente – ej., encontrar una clase con la peor combinación de nuestros criterios – tendríamos un problema clásico de optimización multiobjetiva:

CA y Complejidad Ciclomática


Necesitamos encontrar un punto en el llamado frente de Pareto (rojo en la foto de arriba). Lo interesante sobre el set Pareto es que cada punto en el set es una solución para la prueba de optimización. Cada vez que avanzamos por la línea roja, necesitamos comprometernos con nuestros criterios – si uno mejora el otro empeora. Esto se llama Escalarización y el resultado final depende de cómo se realice.


Hay muchas técnicas que podemos usar aquí. Cada una tiene sus pros y sus contras. Sin embargo, las más populares son escalarización lineal y el que se basa en un punto de referencia. La lineal es la más fácil. Nuestra función fitness se verá como una combinación lineal de CA y Complejidad:


f(CA, Complexity) = A×CA + B×Complexity

donde A y B son algunos coeficientes.


El punto que representa una solución para nuestro problema de optimización está en la línea (azul en la foto debajo). Precisamente, será la intersección de la línea azul y el frente rojo de Pareto. Nuestro problema original no es exactamente un problema de optimización. Pero necesitamos crear una función de categorización. Consideremos dos valores de nuestra función de categorización, básicamente dos valores en nuestra columna Rango.


R1 = A∗CA + B∗Complexity and R2 = A∗CA + B∗Complexity


Algunas de las fórmulas escritas arriba son ecuaciones de líneas, más aún estas líneas son paralelas. Tomando más valores de categorización en consideración tendremos más líneas y por esto más puntos donde intersecta la línea Pareto con las líneas azules (punteadas). Estos puntos serán clases correspondientes a un valor categorizado particular.


Clases Correspondientes a un Valor Categorizado Particular


Desafortunadamente, hay un problema con este acercamiento. Para cualquier línea (Valor Categorizado), tendremos puntos con pequeños CA y muy grande Complejidad (y viceversa) en ella. Esto inmediatamente pone puntos con una gran diferencia entre valores de métrica de primero en la lista, que exactamente lo que queríamos evitar.


La otra manera de hacer la escalarización está basada en el punto de referencia. El punto de referencia es un punto con los valores máximos de ambos criterios:

(max(CA), max(Complexity))


La función fitness será la distancia entre el punto de Referencia y los puntos de data:

f(CA,Complexity) = √((CA−CA )2 + (Complexity−Complexity)2)


Podemos pensar en esta función fitness como un círculo con el centro en el punto de Referencia. El radio en este caso es el valor de la categorización. La solución para el problema de optimización será el punto donde el círculo toca el frente Pareto. La solución al problema original será sets de puntos correspondientes a los distintos radios de círculo como se muestran en la siguiente imagen (partes de círculos para diferentes categorías se muestran como curvas punteadas azules):


Función Fitness


Este acercamiento maneja mejor valores extremos, pero todavía hay dos problemas: Primero – me gustaría tener más puntos cerca de los puntos de referencia para solventar mejor el problema que hemos enfrentado con combinación lineal. Segundo – CA y complejidad Ciclomática son inherentemente diferentes y tienen set de valores diferentes, así que necesitamos normalizarlos (ej. Para que todos los valores de ambas métricas sean de 1 a 100)


Aquí hay un pequeño truco que podemos aplicar para solventar el primer problema – en vez de mirar al CA y a la complejidad ciclomática, podemos mirar sus valores invertidos. El punto de referencia en este caso será (0,0). Para solucionar el segundo problema, podemos normalizar las métricas usando un valor mínimo. Aquí está cómo se ve:


Complejidad normalizada e invertida – NormComplexity:

(1 + min(Complexity)) / (1 + Complexity)∗100

CA invertida y normalizada – NormCA:

(1 + min(CA)) / (1+CA)∗100


Nota: Agregué 1 para asegurarme de que no haya división por 0. t

La siguiente imagen muestra un gráfico con valores invertidos:

Valores Invertidos


Categorización Final

Llegamos al paso final – calcular la categorización. Como mencione, estoy usando el método de punto de referencia, así que lo único que necesitamos hacer es calcular el largo del vector, normalizarlo y hacerlo ascender con la importancia de la creación de una prueba de unidad para una clase. Aquí está la última fórmula:


Rank(NormComplexity , NormCA) = 100 − √(NormComplexity2 + NormCA2) / √2



       Si deseas profundizar la información matemática, puedes acceder al artículo completo en: "Escribe Testeos que Importen: Aborda el Código Mas Complejo Primero" del autor Andrey Shalitkin. 







Comentarios

Entradas populares de este blog

[UPDATE 2020: APPNANA SCAM] AppNana Invitation Codes AppNana Códigos de Invitación

#diy Encender Celular Sin Batería

Ejercicios Resueltos de c++ para Principiantes