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 deben contener. En este artículo, se profundiza en la creación de casos de prueba desde una perspectiva técnica y se detalla qué se debe incluir en cada prueba y qué se debe evitar. En esencia, aprenderás la respuesta a las preguntas eternas de "¿Qué probar?" o "¿Qué no probar?".
Lineamientos y patrones generales
Vale la pena señalar que los patrones y puntos específicos son fundamentales, independientemente de si realizas pruebas de unidades, de integración o de extremo a extremo. Estos principios se pueden y deben aplicar a ambos tipos de pruebas, por lo que son un buen punto de partida.
Sin complicaciones
Cuando se trata de escribir pruebas, uno de los aspectos más importantes que debes recordar es mantener la simplicidad. Es importante considerar la capacidad del cerebro. El código de producción principal ocupa un espacio significativo, lo que deja poco margen para la complejidad adicional. Esto es especialmente cierto para las pruebas.
Si hay menos espacio disponible, es posible que te sientas más relajado en tus esfuerzos de prueba. 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: la prueba debe sentirse como un asistente y no como una fórmula matemática compleja. En otras palabras, deberías poder comprender la intención de la prueba a primera vista.
Debes buscar la simplicidad en todos los tipos de pruebas, independientemente de su complejidad. De hecho, cuanto más compleja sea una prueba, más importante será simplificarla. Una forma de lograrlo es a través de un diseño de prueba plano, en el que las pruebas se mantienen lo más simples posible y solo se prueba lo necesario. Esto significa que cada prueba debe contener un solo caso de prueba, y este debe enfocarse en probar una sola funcionalidad o característica específica.
Piensa en ello desde esta perspectiva: debería ser fácil identificar qué salió mal cuando se lee una prueba fallida. Por eso, es importante que las pruebas sean simples y fáciles de entender. De esta manera, puedes identificar y solucionar problemas rápidamente cuando surjan.
Prueba lo que vale la pena
El diseño de prueba plano también fomenta el enfoque y ayuda a garantizar que tus pruebas sean significativas. Recuerda que no debes crear pruebas solo por la cobertura, sino que siempre deben tener un propósito.
No pruebes los detalles de la implementación
Un problema común en las pruebas es que, a menudo, se diseñan para probar detalles de implementación, como el uso de selectores en componentes o pruebas de extremo a extremo. Los detalles de la implementación se refieren a elementos que los usuarios de tu código no suelen usar, ver ni conocer. Esto puede generar dos problemas principales en las pruebas: falsos negativos y falsos positivos.
Los falsos negativos ocurren cuando una prueba falla, a pesar de que el código probado es correcto. Esto puede suceder cuando cambian los detalles de la implementación debido a una refactorización del código de la aplicación. Por otro lado, los falsos positivos ocurren cuando una prueba es aprobada, a pesar de que el código que se está probando es incorrecto.
Una solución a este problema es considerar los diferentes tipos de usuarios que tienes. Los usuarios finales y los desarrolladores pueden diferir en su enfoque y pueden interactuar con el código de manera diferente. Cuando planificas pruebas, es fundamental tener en cuenta con qué verán o interactuarán los usuarios, y hacer que las pruebas dependan de esos elementos en lugar de los detalles de la implementación.
Por ejemplo, elegir selectores menos propensos a cambios 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. artículo de Dodds sobre este tema o espera a que publiquemos uno más adelante.
Burlas: 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.
Usar simulaciones en tus pruebas puede mejorar la previsibilidad, la separación de preocupaciones y el rendimiento. Además, si necesitas realizar una prueba que requiera la intervención humana (como la verificación de pasaportes), deberás ocultarla con una simulación. Por todos estos motivos, las simulaciones son una herramienta valiosa que debes tener en cuenta.
Al mismo tiempo, la simulación puede 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.
¿Debes simular en pruebas de extremo a extremo?
En general, no. Sin embargo, la simulación puede ser una solución en algunas ocasiones, así que no la descartemos por completo.
Imagina esta situación: estás escribiendo una prueba para una función que involucra un servicio de proveedor de pagos de terceros. 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 funciona mal, lo que hace que tus pruebas fallen. El proveedor de pagos debe corregir el problema. Lo único que puedes hacer es 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 las pruebas.
Detalles de la prueba: Qué hacer y qué no hacer
En resumen, ¿qué contiene una prueba? ¿Hay 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 unidad ideal y eficaz debe cumplir con los siguientes requisitos:
- Concéntrate en aspectos específicos.
- Operar de forma independiente
- Abarca situaciones a pequeña escala.
- Usa nombres descriptivos.
- Sigue el patrón AAA si corresponde.
- Garantiza una cobertura de prueba integral.
Hacer ✅ | No ❌ |
---|---|
Mantén las pruebas lo más pequeñas posible. Prueba un elemento por caso de prueba. | Escribe pruebas en unidades grandes. |
Mantén siempre las pruebas aisladas y simula los elementos que necesitas que están fuera de tu unidad. | Incluye otros componentes o servicios. |
Mantén las pruebas independientes. | Basarse en pruebas anteriores o compartir datos de prueba |
Abarca diferentes situaciones y trayectorias. | Limitate al camino ideal o, como máximo, a las pruebas negativas. |
Usa títulos descriptivos para que puedas ver de inmediato de qué se trata la prueba. | Prueba solo por el nombre de la función, lo que no es lo suficientemente descriptivo como resultado: testBuildFoo() o testGetId() . |
Intenta obtener una buena cobertura de código o una gama más amplia 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, hay algunos puntos adicionales que debes tener en cuenta. Una buena prueba de integración debe hacer lo siguiente:
- Simula las interacciones entre los componentes.
- Abarca situaciones de la vida real y usa simulaciones o stubs.
- Ten en cuenta el rendimiento.
Hacer ✅ | No ❌ |
---|---|
Prueba los puntos de integración: verifica que cada unidad funcione de forma fluida cuando se integren entre sí. | Prueba cada unidad de forma aislada, ya que para eso se realizan las pruebas de unidades. |
Prueba situaciones del mundo real: usa datos de prueba derivados de datos del mundo real. | Usar datos de prueba repetitivos generados automáticamente o datos que no reflejan casos de uso del mundo real |
Usa simulaciones y stubs para las dependencias externas para mantener el control de la 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. | No olvides usar medidas de limpieza en tus pruebas. De lo contrario, se pueden producir fallas o falsos positivos debido a la falta de aislamiento de pruebas adecuado. |
¿Qué pertenece a una buena prueba de extremo a extremo?
Una prueba integral de extremo a extremo debe cumplir con los siguientes requisitos:
- Replica las interacciones del usuario.
- Abarca situaciones vitales.
- Abarcan varias capas.
- Administra operaciones asíncronas.
- Verifica los resultados.
- Ten en cuenta el rendimiento.
Hacer ✅ | No ❌ |
---|---|
Usa combinaciones de teclas impulsadas por la API. Obtén 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 aún más cuidado con el aislamiento de pruebas que en las pruebas de unidades y de integración, ya que existe un mayor riesgo de efectos secundarios. | No olvides limpiar después de cada prueba. Si no limpias el estado, los datos o los efectos secundarios restantes, estos afectarán a 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 la aplicación. | Prueba cada unidad de forma aislada, ya que para eso se realizan las pruebas de unidades. |
Usa una simulación mínima o no la uses en la prueba. Considera cuidadosamente si quieres simular dependencias externas. | Dependen en gran medida de las simulaciones. |
Ten en cuenta el rendimiento y la carga de trabajo, por ejemplo, no realices pruebas excesivas de situaciones grandes en la misma prueba. | Abarca flujos de trabajo grandes sin usar atajos. |