No hace mucho escribí en este blog una entrada describiendo qué son las pruebas de software. En ella planteaba qué son las pruebas de software y por qué son importantes, dando una visión superficial de algunos de los tipos de pruebas de software que existen. Si aun no la has leído te recomiendo leerla antes de continuar.
Nota de campusMVP: En este vídeo, Jorge Turrado (autor del post y de nuestro curso de testing de software) nos hace una introducción a los tipos de test de aplicaciones que complementa muy bien lo expuesto en este artículo.
Asumiendo la gran variedad y cantidad de pruebas que existen a la hora de desarrollar software, es fácil perder la visión sobre qué está en el tejado de quién durante el ciclo de vida de un sistema. Como desarrolladores, las cuestiones de hasta dónde debemos llegar y qué escapa a nuestro control o responsabilidades, suele ser algo bastante difuso. Sin embargo, existe consenso en torno a que los desarrolladores, como mínimo, debemos desarrollar 3 tipos de pruebas sobre el código:
- Pruebas unitarias
- Pruebas de integración
- Pruebas funcionales
Existen otros tipos de pruebas que habitualmente pueden ser responsabilidad de los desarrolladores, pero ya no es algo tan aceptado y depende de a quién preguntes, te dirá una cosa u otra.
Vamos a darle un repaso a estas tres, las más importantes, para ver en qué consisten y qué implicaciones tienen para el desarrollador.
Pruebas unitarias
El primero de los grupos, las pruebas unitarias, es como se conoce a todas esas pruebas que solo comprueban una funcionalidad específica de una única clase. A este tipo de pruebas "les da igual" el comportamiento del resto, ya que la gran mayoría de las veces reciben un objeto que simula el comportamiento externo a esa clase, necesario para funcionar.
La finalidad última de las pruebas unitarias es comprobar funcionalidades muy concretas de cada clase.
Son pruebas que no deberían costarnos más de 5 minutos escribir, y de las cuales vamos a tener tantas como sea necesario para probar el 100% (o lo máximo posible al menos) de los posibles casos que estén contemplados en el código.
Por ejemplo, el hecho de comprobar que un método lanza una excepción en una condición concreta es un caso claro de prueba unitaria.
De este tipo de pruebas se espera que sean rapidísimas de ejecutar, ya que en un proyecto grande habitualmente habrá cientos (¡o incluso miles!) de éstas. Sin ir más lejos, el framework de simulación Moq tiene 2.843 pruebas unitarias, y cada una tarda en ejecutarse del orden de milisegundos.
Pruebas de integración
El segundo de los grupos, las pruebas de integración, es como conocemos a todas esas pruebas que verifican que las relaciones existentes entre las diferentes clases y servicios funcionan correctamente.
Este tipo de pruebas lo que busca es encontrar todos esos problemas que surgen al mezclar las diferentes capas de nuestra aplicación.
Por ejemplo, si trabajamos con una base de datos, en una prueba de integración utilizaremos un motor de base de datos real y no uno en memoria o simulado. De este modo validaremos la correcta integración de nuestro sistema con esa base de datos.
En este tipo de pruebas es posible utilizar simulaciones para algunos niveles que todavía no se pueden probar, como por ejemplo un servicio de terceros.
Suelen ser pruebas más costosas de desarrollar y ejecutar que las pruebas unitarias ya que en cada una de ellas se deben integrar varios puntos del sistema para asegurar que funcionan correctamente en conjunto.
Pruebas funcionales
Las pruebas funcionales verifican el comportamiento del sistema para afirmar que se cumple con la funcionalidad completa. Se les suele denominar también pruebas End-To-End o E2E.
Este tipo de pruebas se realizan con los requisitos de funcionamiento en la mano, y no se centran en los pormenores del sistema, que ya deberían estar probados con los dos grupos anteriores.
Por lo general, son pruebas que requieren de más esfuerzo que las anteriores, ya que en ellas debemos probar el funcionamiento completo del sistema en base a los requisitos existentes. Por esto debemos utilizar el mismo tipo de sistemas que utilizará el código en producción.
Además, es habitual que se hagan pruebas específicas para evaluar el rendimiento y comprobar que está dentro de los parámetros deseados.
Un ejemplo de pruebas funcionales podría ser cuando en ASP.NET Core utilizamos TestServer
*, de modo que estamos probando que el funcionamiento completo es correcto: desde el enrutado hasta la conexión a una base de datos (pasando por cada uno de los componentes internos).
* TestServer
es una clase incluida en ASP.NET Core que nos proporciona un servidor completo, con toda la aplicación en memoria, para poder probarla con gran rendimiento y sin necesidad de configurar y desplegar un servidor completo real.
Relación entre tipos de pruebas
A la hora de desarrollar los diferentes grupos de pruebas, como en casi todo, debemos aplicar el sentido común para conseguir probar el máximo código posible con el menor esfuerzo.
Por ejemplo, no siempre tiene mucho sentido intentar cubrir con pruebas funcionales el 100% de las posibilidades de una clase concreta. El motivo es que, además de que muchas veces no es ni siquiera posible, hacerlo aumentaría enormemente los costes.
En relación a cómo debería ser la cantidad de código a probar, se suele representar como una pirámide en la que las pruebas unitarias constituyen su base, y las pruebas funcionales están en la cúspide:
Esta pirámide se llama pirámide de testing o pirámide de Cohn (se atribuye a Mike Cohn aunque nunca ha quedado clara su autoría). Lo que nos intenta transmitir es que, aunque debes usar el sentido común para cada caso, en general siempre debe haber muchas más pruebas unitarias que de integración y muchas más que test funcionales.
De hecho, si dentro de los test funcionales tuviésemos pruebas de la interfaz de usuario (también llamados test End to End o E2E), estos deberemos mantenerlos al mínimo. El motivo es que este tipo de test, aunque suelen aportan bastante valor, son muy costosos de crear, pero sobre todo, son muy costosos de mantener y no conviene abusar de ellos.