Para probar o no, desde una perspectiva técnica

Determina qué debes probar y qué puedes descartar.

En el artículo anterior, se abordaron los conceptos básicos de los casos de prueba y lo que deberían contener. En este artículo, se profundiza en la creación de casos de prueba desde una perspectiva técnica, se detalla lo que se debe incluir en cada prueba y lo que se debe evitar. En esencia, aprenderás las respuestas a las preguntas antiguas de “¿Qué debes probar?” o “Qué no debes probar”.

Qué probar y qué no

Vale la pena señalar que los patrones y puntos específicos son cruciales, independientemente de si se realizan pruebas de unidades, de integración o de extremo a extremo. Estos principios pueden y deben aplicarse a ambos tipos de pruebas, por lo que son un buen punto de partida.

Sin complicaciones

Cuando se trata de escribir pruebas, una de las cosas más importantes que debes recordar es mantener la sencillez. Es importante considerar la capacidad del cerebro. El código de producción principal ocupa un espacio significativo y deja poco lugar para una complejidad adicional. Esto es especialmente cierto para las pruebas.

Si hay menos espacio de trabajo disponible, es posible que relajes más a la hora de realizar pruebas. Por eso, es fundamental priorizar la simplicidad en las pruebas. De hecho, las prácticas recomendadas para pruebas de JavaScript de Yoni Goldberg enfatizan la importancia de la regla de oro: tu prueba debe parecer un asistente y no una fórmula matemática compleja. En otras palabras, debes poder comprender el intent de la prueba a primera vista.

No hagas que las pruebas sean complejas, no deberían sentirse así.

Tu objetivo debe ser la simplicidad en todos los tipos de pruebas, sin importar su complejidad. De hecho, cuanto más compleja es una prueba, más importante será simplificarla. Una forma de lograrlo es mediante un diseño de prueba plano, en el que las pruebas se mantengan lo más simples posible, y solo probar lo que sea necesario. Esto significa que cada prueba debe contener solo un caso de prueba, y el caso de prueba debe enfocarse en probar una funcionalidad o función única y específica.

Piénsalo desde esta perspectiva: debería ser fácil identificar qué salió mal al leer una prueba fallida. Por este motivo, es importante mantener las pruebas simples y fáciles de entender. De esta manera, podrás identificar y solucionar rápidamente los problemas que surjan.

Prueba lo que vale la pena

El diseño de prueba plana también fomenta el enfoque y ayuda a garantizar que las pruebas sean significativas. Recuerda que no es conveniente crear pruebas solo para fines de cobertura; siempre deben tener un propósito.

No pruebes todo.

No probar los detalles de la implementación

Un problema común en las pruebas es que estas suelen diseñarse para probar detalles de implementación, como el uso de selectores en componentes o pruebas de extremo a extremo. Los detalles de implementación hacen referencia a elementos que los usuarios de tu código no suelen usar, ver ni conocer. Esto puede generar dos problemas importantes en las pruebas: falsos negativos y falsos positivos.

Los falsos negativos se producen cuando falla una prueba, a pesar de que el código probado es correcto. Esto puede ocurrir cuando los detalles de la implementación cambian debido a una refactorización del código de la aplicación. Por otro lado, los falsos positivos se producen cuando se aprueba una prueba, aunque el código que se está probando sea incorrecto.

Una solución a este problema es considerar los diferentes tipos de usuarios que tienes. Los usuarios finales y los desarrolladores pueden tener un enfoque diferente y es posible que interactúen con el código de manera diferente. Cuando planifiques las pruebas, es esencial tener en cuenta con qué verán los usuarios o con qué interactuarán, y hacer que las pruebas dependan de esos elementos en lugar de los detalles de la implementación.

Por ejemplo, elegir selectores que son menos propensos a cambiar puede hacer que las pruebas sean más confiables: atributos de datos en lugar de selectores CSS. Para obtener más información, consulta Kent C. Dodds sobre este tema o manténganse al tanto, ya que pronto publicaremos un artículo sobre este tema.

Simulación: No pierdas el control

La simulación es un concepto amplio que se usa en las pruebas de unidades y, a veces, en las pruebas de integración. Implica crear datos o componentes falsos para simular dependencias que tienen control total sobre la aplicación. Esto permite realizar pruebas aisladas.

El uso de simulaciones en tus pruebas puede mejorar la previsibilidad, la separación de problemas y el rendimiento. Además, si necesitas realizar una prueba que requiera la participación humana (como la verificación de un pasaporte), tendrás que ocultarla con un simulacro. Por todas estas razones, las simulaciones son una herramienta valiosa para tener en cuenta.

Al mismo tiempo, las simulaciones pueden afectar la precisión de la prueba porque son simulaciones, no las experiencias reales del usuario. Por lo tanto, debes tener cuidado cuando uses simulaciones y stubs.

¿Es necesario simular en pruebas de extremo a extremo?

En general, no. Sin embargo, a veces, las burlas pueden ser un salvavidas, así que no debemos descartarlo por completo.

Imagina que estás escribiendo una prueba para una función que involucra el servicio de un proveedor de pagos externo. Estás en un entorno de zona de pruebas que te proporcionaron, lo que significa que no se están realizando transacciones reales. Lamentablemente, la zona de pruebas no funciona correctamente, lo que provoca que las pruebas fallen. El proveedor de pagos debe realizar la corrección. Solo puedes esperar a que el proveedor resuelva el problema.

En este caso, podría ser más beneficioso disminuir la dependencia de los servicios que no puedes controlar. De todas formas, se recomienda usar la simulación con cuidado en las pruebas de integración o de extremo a extremo, ya que disminuye el nivel de confianza de tus pruebas.

Información específica de las pruebas: Sugerencias y precauciones

Entonces, ¿qué contiene una prueba? ¿Existen diferencias entre los tipos de pruebas? Analicemos con más detalle algunos aspectos específicos adaptados a los principales tipos de pruebas.

¿Qué pertenece a una buena prueba de unidades?

Una prueba de unidades ideal y eficaz debe cumplir con los siguientes requisitos:

  • Concéntrate en aspectos específicos.
  • Funcionan de forma independiente.
  • Pueden abarcar escenarios de menor escala.
  • Usa nombres descriptivos.
  • Siga el patrón AAA si corresponde.
  • Garantiza una cobertura de prueba integral.
Lo que no ❌
Haz que las pruebas sean lo más pequeñas posible. Prueba un solo paso por caso de prueba. Escribe pruebas en unidades grandes.
Siempre mantén las pruebas aisladas y simula las tareas que necesitas que estén fuera de la unidad. Incluir otros componentes o servicios
Mantén las pruebas independientes. Usa pruebas anteriores o comparte datos de pruebas.
Abarca diferentes situaciones y rutas. Limitarte al camino ideal o a las pruebas negativas como máximo.
Usa títulos descriptivos de pruebas para poder ver de inmediato de qué se trata. Realiza pruebas solo con el nombre de la función (no debes ser lo suficientemente descriptivo: testBuildFoo() o testGetId()).
Procura tener una buena cobertura de código o una mayor variedad de casos de prueba, especialmente en esta etapa. Realiza pruebas desde todas las clases hasta el nivel de la base de datos (E/S).

¿Qué pertenece a una buena prueba de integración?

Una prueba de integración ideal también comparte algunos criterios con las pruebas de unidades. Sin embargo, existen algunos puntos adicionales que debes considerar. Para que una prueba de integración sea excelente:

  • Simula interacciones entre componentes.
  • Abarca escenarios del mundo real y usa simulaciones o stubs.
  • Ten en cuenta el rendimiento.
Lo que no ❌
Prueba los puntos de integración: Verifica que cada unidad funcione correctamente cuando se integren entre sí. Prueba cada unidad de forma aislada: para eso están las pruebas de unidades.
Prueba situaciones del mundo real: utiliza datos de prueba derivados de datos del mundo real. Usa datos de prueba repetitivos generados automáticamente, o bien otros datos que no reflejen casos de uso del mundo real.
Usa simulaciones y stubs para dependencias externas para mantener el control de tu prueba completa. Crear dependencias en servicios de terceros, por ejemplo, solicitudes de red a servicios externos
Usa una rutina de limpieza antes y después de cada prueba. Olvídate de usar medidas de limpieza dentro de tus pruebas. De lo contrario, esto puede provocar fallas en las pruebas o falsos positivos debido a la falta de un aislamiento adecuado.

¿Qué se considera una buena prueba integral?

Una prueba completa de extremo a extremo debe cumplir con los siguientes requisitos:

  • Replica las interacciones del usuario.
  • Abarca situaciones cruciales.
  • Abarca varias capas.
  • Administra operaciones asíncronas.
  • Verifica los resultados.
  • Ten en cuenta el rendimiento.
Lo que no ❌
Usar combinaciones de teclas basadas en la API Más información. Usa interacciones de la IU para cada paso, incluido el hook beforeEach.
Usa una rutina de limpieza antes de cada prueba. Ten mucho más cuidado con el aislamiento de pruebas que en las pruebas de integración y de unidades, ya que aquí existe un mayor riesgo de efectos secundarios. Olvídate de limpiar después de cada prueba. Si no limpias el estado restante, los datos o los efectos secundarios, estos afectarán otras pruebas que se ejecuten más adelante.
Considera las pruebas de extremo a extremo como pruebas del sistema. Esto significa que debes probar toda la pila de aplicaciones. Prueba cada unidad de forma aislada: para eso están las pruebas de unidades.
Usa una simulación mínima o ninguna dentro de la prueba. Piensa bien si deseas simular dependencias externas. Depender mucho de las simulaciones.
Considera el rendimiento y la carga de trabajo, por ejemplo, no realices pruebas excesivas de situaciones grandes en la misma prueba. Cubre flujos de trabajo grandes sin usar accesos directos.