¿Qué es la prueba unitaria?

Pruebas Unitarias: Un tutorial sobre lo que es, cómo hacerlo y las herramientas que se pueden utilizar

Las pruebas unitarias son esenciales. Sin ellas, se camina sobre cáscaras de huevo todo el tiempo.

Eso es lo que se oye, al menos.

Pero ¿por qué? ¿Y qué es, de todos modos?

Vamos a profundizar en ello.

¿Qué son las Pruebas Unitarias?

unit-testing

Las pruebas unitarias consisten en verificar el comportamiento de las unidades más pequeñas de su aplicación.

Técnicamente, eso sería una clase o incluso un método de clase en los lenguajes orientados a objetos, y un procedimiento o función en los lenguajes procedimentales y funcionales.

Funcionalmente, puede ser un conjunto de clases estrechamente relacionadas. Como un `Ciervo` y sus clases de apoyo `Cabeza`, `Cola` y `Locomoción`.

Lo que consideres una unidad de trabajo es lo que tenga sentido para ti. No hay reglas estrictas. Siempre y cuando le ayude a entender y pensar en su aplicación.

Eso es diferente a lo que es una prueba de unidad.

¿Qué hace que una prueba unitaria sea una prueba unitaria?

Michael Feathers (autor de «Working Effectively with Legacy Code») sitúa la distinción en el nivel del proceso y del sistema.

Las pruebas unitarias deben ejecutarse de forma aislada porque tienen que ser rápidas.

Todo el conjunto de pruebas unitarias de una aplicación debe ejecutarse en minutos, preferiblemente en segundos. Más adelante se verá por qué.

Por eso las pruebas unitarias no pueden utilizar ningún proceso o sistema externo. No hay operaciones de E/S de ningún tipo (base de datos, archivo, consola, red), excepto el registro de los fallos de las pruebas y quizás la lectura de la configuración de conmutación de características por defecto al inicio de una ejecución de prueba.

Si su código de prueba (o las bibliotecas que utiliza) hace E/S o accede a algo fuera de su proceso, no es una prueba unitaria, sino una prueba de integración.

¿Cuál es el propósito de las pruebas unitarias?

SwiftKanban

Muchos dicen que el propósito de las pruebas unitarias es validar que cada unidad de trabajo se comporta como se ha diseñado, esperado o previsto. Y que puedes ejecutar tus pruebas unitarias cada vez que alguien haga un cambio. Con sólo pulsar un botón.

Pero el verdadero propósito de las pruebas unitarias es proporcionarle una retroalimentación casi instantánea sobre el diseño y la implementación de su código.

La creación de pruebas unitarias, o más bien la facilidad o dificultad con la que puedes crearlas te dice precisamente cómo de comprobable es su código. Cómo de bien lo ha diseñado. Y si lo escucha, en lugar de usar hacks para superar cualquier dificultad, todos ganan.

He aquí cómo.

¿Cuáles son los beneficios de las pruebas unitarias?

Las pruebas unitarias tienen muchas ventajas.

  • Se ahorra mucho tiempo en las pruebas de regresión.
  • Recibe una alerta cada vez que rompe inadvertidamente un comportamiento existente. Lo que le permite abordarlo inmediatamente mientras sigue inmerso en lo que está trabajando. Lo que significa que hay menos errores que se escapan a la naturaleza.
  • Menos errores que se escapan significan que tienes más tiempo para crear valor.
  • Al trabajar sin miedo a romper el código existente sin saberlo, tienes más recursos cognitivos valiosos. Lo que significa que será más creativo e innovador, y capaz de crear mejores soluciones.
  • Obtiene una documentación viva, que respira y que nunca se desactualiza, al menos cuando le da a cada prueba de unidad un nombre significativo (más sobre esto más adelante).
  • La documentación siempre actualizada le permite poner al día a los nuevos contratados más rápidamente.
  • Al trabajar sin miedo a romper el código existente sin saberlo, los nuevos miembros del equipo se vuelven productivos antes.
  • Un menor número de errores también significa que reducirá la carga del personal de soporte y los liberará para que se centren en el éxito del cliente en lugar de controlar los daños.

Seguro que a estas alturas se está preguntando cómo conseguir todo esto.

Cómo Escribir Pruebas Unitarias (Usando las Mejores Prácticas)

Crear pruebas unitarias es lo mismo que desarrollar cualquier código, pero hay una diferencia.

Usted crea código de aplicación funcional para resolver un problema para sus clientes.

Usted crea pruebas unitarias para resolver los problemas que surgen en el desarrollo de ese código de aplicación.

Adivine qué significa eso.

Sí, ¡usted es su propio cliente! Y, por supuesto, quiere facilitarse la vida lo máximo posible. 

Así que aproveche estas mejores prácticas.

  • Asigne a sus métodos de prueba nombres que le ayuden a entender los requisitos del código que está probando sin tener que buscar en otra parte. Escoja una de las varias  plantillas probadas y comprobadas que existen para esto y adhiérase a ella.
  • Asegúrese de que una prueba sólo tiene éxito porque el código que prueba es correcto. Del mismo modo, asegúrese de que una prueba sólo falla porque el código que prueba es incorrecto. Cualquier otra razón para el éxito o el fracaso es engañarse a sí mismo o perseguir un agujero de conejo.
  • Esfuércese por crear mensajes de fallo breves y significativos que incluyan parámetros de prueba relevantes. Hay pocas cosas más frustrantes que «Se esperaba 5, pero se encontró 7» y tener que buscar el valor de los parámetros del método bajo prueba.
  • Asegúrese de que cada prueba puede producir los resultados correctos (éxito o fracaso) incluso cuando es la única prueba que se ejecuta.

Cuando una prueba depende de cómo otra prueba ha cambiado el entorno (valores de las variables, contenido de las colecciones, etc.), le resultará difícil hacer un seguimiento de las condiciones iniciales de cada prueba. Y lo que es más importante, cuando obtenga resultados inesperados, se preguntará siempre si las condiciones de la prueba o el código de producción los han provocado.

  • No optimice la configuración de las pruebas más allá del archivo fuente de una clase de prueba. Mantenga una clase de prueba como un mundo en sí mismo, independiente de los demás..

Utilice métodos de configuración y clases de utilidad anidadas si lo desea, pero evite las jerarquías de clases de prueba y las clases de utilidad generales. Se ahorrará tener que cazar entre varias clases base o unidades de clases de utilidad para encontrar los valores que utiliza una prueba.

  • Siga el patrón de Organizar, Actuar, Afirmar. Marque cada parte con un comentario. La razón para seguir este patrón está en el siguiente punto.
  • Cada prueba debe tratar sólo::
  • Un arreglo – Un escenario a probar (un «dado»).
  • Una acción – Un método a probar (un «cuando»).
  • Una afirmación – Una llamada a un método de verificación (un «entonces»). 

Cuando una acción deba tener más de un efecto, compruebe cada uno de ellos con un método diferente. Cuando sienta la tentación de utilizar más de una afirmación, pregúntese si está afirmando el hecho más significativo.

  • Cuando se sienta frustrado por lo difícil que es organizar una prueba, escuche ese dolor. Acepte la indirecta de que el diseño de su código necesita mejorar.

Resist the urge to use hacks and “automagic.” Hacks only ever address symptoms, and automagic reduces the transparency you need to figure out why a test fails that should succeed or, worse, why a test succeeds that should fail.

Abordar lo que está causando el dolor utilizando los medios más directos posibles. A menudo, puedes hacer cambios sencillos para que al menos parte de una clase sea más comprobable. Working effectively with Legacy Code de Michael Feathers es un excelente recurso para esto.

Obstáculos comunes en las pruebas unitarias

Los errores más simples pueden hacernos tropezar. Y lo que es peor, pueden hacer que se sienta falsamente seguro.

Estos son los errores que debe evitar

  • Escribir pruebas sin aserciones.

Una prueba así nunca fallará. Si eso está bien porque simplemente está verificando que no ocurren excepciones, hágalo explícito con un assert y un mensaje apropiado.

  • Escribir más de una prueba a la vez.

Es una indicación de que está probando después del hecho (en lugar de un enfoque de prueba primero), y está creando dolores de cabeza para sí mismo en el seguimiento de las pruebas que están completos y deben tener éxito y que las pruebas fallan porque están incompletos.

  • No empezar con una prueba fallida.

Cuando no se empieza con una prueba que falla, no se sabe si tiene éxito porque hay un error en la prueba o porque el código funcional es correcto.

  • No ejecutar las pruebas con frecuencia.
  • Lo ideal es ejecutar todas las pruebas unitarias en cada etapa de un ciclo rojo-verde-refactor, tanto si se utiliza con o sin un enfoque de pruebas como  TDD y BDD.
  • Escribir pruebas lentas.

Slow tests interrupt your flow.

Por cierto, está bien utilizar un marco de pruebas unitarias (ver más abajo) para escribir pruebas lentas, pero no son pruebas unitarias, y es mejor mantenerlas en un conjunto de pruebas separado.

  • Hacer que las pruebas dependan de su entorno de pruebas.

Eso le dará dolores de cabeza cuando las pruebas fallen en un entorno y tengan éxito en otro.

Quédese conmigo ahora. Ya casi ha llegado.

Aquí tiene algunas herramientas y técnicas que puedes utilizar.

Herramientas y técnicas de pruebas unitarias

Marcos de pruebas unitarias

Unit Test Frameworks

Los marcos de pruebas unitarias proporcionan todo lo necesario para crear pruebas unitarias

  • Atributos de código para que un ejecutor de pruebas (véase más adelante) pueda descubrir y ejecutar sus pruebas.
  • Atributos de código para proporcionar a sus pruebas datos de prueba.
  • Clases y métodos para verificar los efectos de la acción que desea probar.

Puede encontrarse uno o más marcos de pruebas unitarias para casi todos los lenguajes de programación que existen.

Ejecutores de pruebas unitarias

Los ejecutores de pruebas unitarias descubren las pruebas en su código de prueba automáticamente, ejecutan todas las pruebas o una selección específica, y luego informan de los resultados de las pruebas.

Vienen como extensiones del IDE y como utilidades de línea de comandos independientes. Estos últimos pueden utilizarse en los scripts de compilación, para que las compilaciones de integración fallen cuando una fusión rompa el código existente.

Los marcos de pruebas unitarias tienen sus propios ejecutores, pero también se pueden encontrar ejecutores dedicados que pueden descubrir y ejecutar pruebas escritas con múltiples marcos.

Bibliotecas de Imitación

Las librerías de imitación le permiten crear dobles de prueba, o fakes, fácilmente.

Se utilizan para proporcionar a una clase bajo prueba instancias de sus clases colaboradoras. De esta manera, puede personalizar fácilmente el comportamiento de los colaboradores a las necesidades de su prueba.

Inyección de dependencia / Inversión de control

La inversión de control, o inyección de dependencia, es un patrón de diseño que se utiliza para romper los lazos duros entre las clases.

En lugar de que la clase A instancie una clase B5, le proporciona una instancia del ancestro más abstracto de B5 que le da a A los métodos y propiedades que necesita.

Esto hace que las pruebas unitarias de una clase con colaboradores sean más fáciles porque es mucho más fácil proporcionarle falsificaciones.

Recursos para las pruebas unitarias

Unit Testing Resources

Entonces, vaya. Pruebe su camino hacia una codificación segura

Así que, eso es todo, todo lo que necesita saber para empezar su carrera de pruebas unitarias.

Nunca más tendrá que andar sobre cáscaras de huevo cuando cambie su código.

Unit Testing: A Tutorial on What It Is, How To Do It + Tools To Use

¡La vida es buena cuando sus equipos ágiles están sincronizados!

Solicite una demostración personalizada de SwiftEnterprise.