No todo el mundo tiene muy clara la diferencia entre pruebas unitarias y de integración. Y la terminología en los frameworks de desarrollo, como pueden ser Eclipse o .NET, no ayuda en este sentido, pues suelen denominar como “Unit Test” a todo tipo de pruebas, unitarias, de integración, de aceptación… y además utilizan las mismas plantillas de código en todos los casos.
Al final, como dice Steve Smith en el artículo de la vía, los desarrolladores acabamos llamando “prueba unitaria” a una entidad de mayor envergadura.
Entonces, ¿cómo se defien una prueba unitaria?
Prueba unitaria: prueba un único método de una clase. El alcance es muy reducido y está perfectamente acotado. Cualquier dependencia del módulo bajo prueba debe ser sustituida por un mock, o un stub.
En Stackoverflow hay un thread en el que aparecen esta y otras definiciones, como la de prueba de integración:
Prueba de integración: prueba la interacción entre dos o más elementos, que pueden ser clases, módulos, paquetes, subsistemas, etc… incluso la interacción del sistema con el entorno de producción.
Para Steve Smith, un test unitario es aquél que prueba un “único camino dentro de un método concreto”. Y esto es muy importante: es un test que NO tiene absolutamente ninguna dependencia externa. Cualquier dependencia debe ser sustituida por un mock, stub, driver… lo que sea necesario.
Cualquier dependencia (con otras partes de código, con el entorno, etc.) convierte tu test en una prueba de integración.
Necesidad de las pruebas unitarias. Construcción de los mocks
La definición es muy clara, pero ¿en qué casos es necesario ejecutar pruebas estrictamente unitarias?
Mi opinión es que sólo deberían hacerse para métodos críticos desde el punto de vista de la funcionalidad del sistema.
La razón es el coste. Supongamos que vamos a probar un método con dos parámetros enteros, con valores válidos en un rango determinado, complejidad ciclomática 2 y dependencia con otro módulo que devuelve un valor entero (con rango válido) y puede lanzar una excepción. A priori, cabe pensar que para alcanzar un 100% de cobertura de decisión, y probar valores válidos, inválidos y límites, se necesitan del orden de 10-20 pruebas, además de la implementación de 3-4 mocks.
Ya sé que la aproximación es muy rudimentaria (por no decir chapucera), pero es evidente que el número de pruebas unitarias para alcanzar una cobertura aceptable de TODO el código sería un orden de magnitud superior al número de métodos implementados, y a esto hay que añadir la implementación de stubs y drivers. Es fundamental, pues, identificar los métodos/clases/subsistemas que requieren este tipo de pruebas.
Aproximación
Como sugería en otra entrada, algunas de estar pruebas unitarias pueden ser sustituidas por pruebas de integración si consideramos que el método del que depende nuestro método bajo prueba no es más que “mock muy elaborado”. La condición previa es que este “mock” haya sido probado previamente. En otro caso, no podemos asegurar que el resultado de la prueba sea correcto.
Cierto que las pruebas de integración son más lentas que las unitarias en ejecución, pero son igualmente automatizables (en raras ocasiones el desarrollo de un módulo va por delante del desarrollo de sus dependencias), y, puesto que los diseños detallados (al menos en nuestro país) brillan por su ausencia, dan una mejor idea del estado global de completitud del proyecto, pues aportan imformación, aunque se parcial, de la funcionalidad desarrollado hasta el momento.
Además, las pruebas de integración presentan una ventaja: las entradas y resultados obtenidos debe tener, al menos, consistencia lógica desde el punto de vista funcional, mientras que las unitarias suelen ser mucho más abstractas.
Conclusión
Ejecutar las pruebas unitarias mínimas necesarias para asegurar una robustez acorde a la criticidad de las aplicaciones desarrolladas. Para todo lo demás, de integración y funcionales.
