Se cree que no se necesitan pruebas unitarias. Que solo la mitad de la verdad está oculta en ellos. Y esa información genuina sobre el comportamiento del programa se revelará solo cuando la recopilemos en una prueba de integración.
Hay una razón para esto, pero ¿las pruebas unitarias son realmente incompletas y pueden hacerse más confiables? ¿Cuántas razones de su incompletitud?
Supongamos que tenemos dos componentes cubiertos por pruebas unitarias, Caller y Callee. La persona que llama llama a Callee con un argumento y de alguna manera usa el objeto devuelto. Cada uno de los componentes tiene su propio conjunto de dependencias, que remojamos.
¿Cuántos escenarios en los que estos componentes se comportan inesperadamente durante la integración?
El primer escenario es un
problema externo a ambos componentes . Por ejemplo, ambos dependen del estado de la base de datos, autorización, variables de entorno, variables globales, cookies, archivos, etc. Juzgar esto es bastante simple, porque incluso en programas muy grandes generalmente hay un número limitado de tales puntos de contención.
El problema se puede resolver, obviamente, ya sea a través de un rediseño con dependencias reducidas,
o simulamos directamente un posible error en un escenario de nivel superior, es decir, presentamos el componente CallingStrategy (OffendingCaller, OffendedCallee) {}, y simulamos el manejo de errores y errores de Callee en CallingStrategy. Para esto, no se requieren pruebas de integración, pero se requiere un entendimiento de que el comportamiento específico de uno de los componentes plantea un riesgo para el otro componente, y sería bueno aislar este escenario en un componente.
Segundo escenario: el problema está en la interfaz de los objetos integrables, es decir
un estado innecesario de uno de los objetos se ha filtrado a otro .
De hecho, esta es una falla en la interfaz que lo permite. La solución al problema también es bastante obvia: tipeo y estrechamiento de interfaces, validación temprana de parámetros.
Como podemos ver, ambas razones son extremadamente comunes, pero sería bueno articular claramente que no hay otras.
Por lo tanto, si verificamos nuestras clases para 1) estado interno y 2) dependencias externas, entonces no tenemos razón para dudar de la confiabilidad de las pruebas unitarias.
(En algún lugar de la esquina, un programador funcional está llorando en voz baja con las palabras "te lo dije", pero no por eso en este momento).
¡Pero podemos olvidarnos o perdernos algún tipo de adicción!
Se puede estimar groseramente. Supongamos que hay diez escenarios en cada componente. Nos saltamos un escenario de cada diez. Por ejemplo, Callee de repente devuelve nulo y Caller recibe de repente una NullPointerException. Necesitamos cometer un error dos veces, lo que significa que la probabilidad de caer en algún lugar es 1/100. Es difícil imaginar que el escenario de integración para los dos elementos captará esto. Para muchos componentes llamados secuencialmente dentro de la prueba de integración, la probabilidad de detectar algunos de los errores aumenta, lo que significa que cuanto más larga sea la pila de la prueba de integración y más escenarios, más justificada está.
(La matemática real de la acumulación de errores es, por supuesto, mucho más complicada, pero el resultado no varía mucho).
Sin embargo, en el proceso de realizar la prueba de integración, podemos esperar un aumento significativo en el ruido de las dependencias interrumpidas y un tiempo significativo dedicado a encontrar un error, también son proporcionales a la longitud de la pila.
Es decir, resulta que las
pruebas de integración son necesarias si las pruebas unitarias son malas o faltan . Por ejemplo, cuando solo se verifica un script válido en cada prueba unitaria, cuando usan interfaces demasiado amplias y no analizan dependencias generales.