Saltar a contenido

Unit Testing

¿Qué es un Unit Test?

El Unit Test o prueba unitaria son uno de los muchos tipos diferentes de pruebas automatizadas. Las pruebas unitarias ejercitan partes muy pequeñas de la aplicación en completo aislamiento, comparando su comportamiento real con el comportamiento esperado. La parte de "aislamiento completo" significa que, cuando se realizan pruebas unitarias, normalmente no conecta su aplicación con dependencias externas como bases de datos, el sistema de archivos o servicios HTTP. Eso permite que las pruebas unitarias sean más rápidas y estables ya que no fallará debido a problemas con esos servicios externos.

piramide

Motivos para realizar Unit Test

  • Las pruebas unitarias demuestran que la lógica del código está en buen estado y que funcionará en todos los casos.
  • Aumentan la legibilidad del código y ayudan a los desarrolladores a entender el código base, lo que facilita hacer cambios más rápidamente.
  • Los test unitarios bien realizados sirven como documentación del proyecto.
  • Se ejecutan en pocos milisegundos, por lo que podrás realizar cientos de ellas en muy poco tiempo.
  • Las unit testing permiten al desarrollador refactorizar el código más adelante y tener la garantía de que el módulo sigue funcionando correctamente. Para ello se escriben casos de prueba para todas las funciones y métodos, para que cada vez que un cambio provoque un error, sea posible identificarlo y repararlo rápidamente.
  • La calidad final del código mejorará ya que, al estar realizando pruebas de manera continua, al finalizar el código será limpio y de calidad.
  • Como las pruebas unitarias dividen el código en pequeños fragmentos, es posible probar distintas partes del proyecto sin tener que esperar a que otras estén completadas.

Regla 3 A's o Patrón AAA

Para llevar a cabo buenas pruebas unitarias, deben estar estructuradas siguiendo las tres A 's del Unit Testing. Se trata de un concepto fundamental respecto a este tipo de pruebas, que describe un proceso compuesto de tres pasos.

  1. Arrange (organizar). Es el primer paso de las pruebas unitarias. En esta parte se definen los requisitos que debe cumplir el código.
  2. Act (actuar). Es el paso intermedio de las pruebas, el momento de ejecutar el test que dará lugar a los resultados que deberás analizar.
  3. Assert (afirmar). En el último paso, es el momento de comprobar si los resultados obtenidos son los que se esperaban. Si es así, se valida y se sigue adelante. Si no, se corrige el error hasta que desaparezca.

Carácteristicas de un buen Unit Test

  1. Rápido: No es raro que los proyectos maduros tengan miles de pruebas unitarias. Las pruebas unitarias deberían tomar muy poco tiempo para ejecutarse. Milisegundos.
  2. Aislado: Las pruebas unitarias son independientes, se pueden ejecutar de forma aislada y no dependen de ningún factor externo, como un sistema de archivos o una base de datos.
  3. Repetible: La ejecución de una prueba unitaria debe ser coherente con sus resultados, es decir, siempre devuelve el mismo resultado si no cambia nada entre ejecuciones.
  4. Autocomprobación: La prueba debería poder detectar automáticamente si pasó o falló sin ninguna interacción humana.
  5. Oportuno: Una prueba de unidad no debería tardar un tiempo desproporcionadamente largo en escribirse en comparación con el código que se está probando. Si encuentra que probar el código toma una gran cantidad de tiempo en comparación con escribir el código, considere un diseño que sea más comprobable.

Buenas Prácticas para los Unit Test

  1. Las pruebas unitarias deberían ser independientes. Si se produce cualquier tipo de mejora o cambio en los requerimientos, las pruebas unitarias no deberían verse afectadas.
  2. Prueba sólo un código a la vez.
  3. Sigue un esquema claro. Puede parecer algo secundario, pero no lo es. Sé también consistente a la hora de nombrar tus unit tests.
  4. Cualquier cambio necesita pasar el test. En el caso de producirse un cambio en el código de cualquier módulo, asegúrate de que hay una prueba unitaria que se corresponda con ese módulo y que este pasa las pruebas antes de cambiar la implementación.
  5. Corrige los bugs identificados durante las pruebas antes de continuar. Asegúrate de realizar esta corrección antes de proseguir con la siguiente fase del ciclo de vida del desarrollo de software.
  6. Acostúmbrate a realizar pruebas regularmente mientras programas. Cuanto más código escribas sin testar, más caminos tendrás que revisar para encontrar errores.

Code Coverage

La cobertura de código es un método de análisis que determina qué partes de nuestro código han sido cubiertas, es decir, han sido ejecutadas por las pruebas unitarias, y qué partes no lo han sido.

Un informe de cobertura de código tiene muchísimas ventajas, ya que nos permite saber en todo momento si nuestras pruebas unitarias están cubriendo la mayor parte de nuestro código o no.

Un alto porcentaje de cobertura de código a menudo se asocia con una mayor calidad de código. Sin embargo, la medida en sí no puede determinar la calidad del código. Esto es debido a que el code coverage dicta que líneas de código han sido ejecutadas en un test pero no si el test se realizó correctamente o si alcanza los escenarios posibles a analizar.

Ejemplo - veamos el siguiente método:

def suma(a, b):
    return a + b
Piensen: ¿Cómo probarían este método? ¿Cuántos escenarios posibles tengo?

Si realizo un test pasándole dos valores numéricos, ejemplo: a=2 y b=3 y sí comprobará el resultado de esa función y este es 6 entonces tendría el escenario esperado, el test daría 'Ok' y tendría un 100% de code coverage.

Pero ¿Es el único escenario? que pasa sí a=null o b=null o que pasaría si a fuese a='hola', etc.

Con este escenario tan sencillo demostramos que el Code Coverage no garantiza la calidad de los Test sino simplemente tener una métrica de las lineas que tengo cubiertas. La calidad de los Test se lo dan los propios desarrolladores conociendo el código que realizaron y las necesidades de negocio que debe cumplir cada unidad de código.

Dobles de pruebas

Un mock es un tipo concreto de doble de test. La expresión doble se usa en el mismo sentido de los actores dobles en las películas de acción, ya que se hace pasar por un colaborador del SUT cuando en realidad no es la entidad que dice ser. Gerard Meszaros describe los distintos tipos de dobles de test en su libro donde, además, sienta las bases de la nomenclatura. Martin Fowler publicó un artículo que se ha hecho muy popular basado en esta nomenclatura; "Los mocks no son stubs", donde habla de los distintos dobles. De ahí extraemos el siguiente listado de tipos de doble:

  • Dummy: se pasa como argumento pero nunca se usa realmente. Normalmente, los objetos dummy se usan sólo para rellenar listas de parámetros.
  • Fake: tiene una implementación que realmente funciona pero, por lo general, toma algún atajo o cortocircuito que le hace inapropiado para producción (como una base de datos en memoria por ejemplo).
  • Stub: proporciona respuestas predefinidas a llamadas hechas durante los tests, frecuentemente, sin responder en absoluto a cualquier otra cosa fuera de aquello para lo que ha sido programado. Los stubs pueden también grabar información sobre las llamadas; tal como una pasarela de email que recuerda cuántos mensajes envió.
  • Mock: objeto preprogramado con expectativas que conforman la especificación de cómo se espera que se reciban las llamadas. Son más complejos que los stubs aunque sus diferencias son sutiles.

piramide

TDD

El test driven development (TDD) o desarrollo guiado por pruebas implica desarrollar las pruebas unitarias a las que se va a someter el software antes de escribirlo. De esta manera, el desarrollo se realiza atendiendo a los requisitos que se han establecido en la prueba que deberá pasar. El objetivo es conseguir un código limpio que funcione. Con esta metodología, se escoge un requisito de la lista y se plantea una prueba que se ejecuta para comprobar que falla. Si no falla puede ser porque no se ha planteado correctamente o porque la función ya estaba implementada. A continuación, se escribe el código que haga posible pasar la prueba de la manera más simple posible, se ejecutan las pruebas y, si todo es correcto, se refactoriza el código para eliminar las partes duplicadas. Así se puede tachar ese requisito de la lista y seguir avanzando con el desarrollo. Puede profundizar en esta técnica en #TDD.

piramide

Recursos:

Autor/a: Olivera Lucas

Contacto: lolivera@andreani.com