qué tipo de funciones y / o clases son imposibles de probar y por qué

21

La principal excusa del desarrollador para no tener una buena unidad de prueba es "El código no está diseñado de manera comprobable". Estoy tratando de entender qué tipo de diseño y código no se pueden probar en la unidad.

manizzzz
fuente
2
Tu excusa? A compañeros de trabajo? Gerentes? ¿Con qué idioma / marcos estás trabajando?
1
Una gran cantidad de código heredado en la aplicación y no hay tiempo para rediseñar.
knut
44
@gnat: no estoy de acuerdo. La pregunta que citó es sobre situaciones en las que las pruebas unitarias no son útiles. La pregunta actual es sobre situaciones que dificultan las pruebas unitarias.
Arseni Mourzenko
@MainMa aparentemente leemos diferentes preguntas. "Estoy tratando de entender qué tipo de diseño y código no se pueden probar en la unidad". => "¿Cuándo es apropiado no realizar pruebas unitarias?"
mosquito
1
@manizzzz: es posible que desee editar eso en la pregunta.
jmoreno

Respuestas:

27

Varios factores pueden dificultar la prueba unitaria del código. Cuando este es el caso, la refactorización ayuda a mejorar el código para que sea comprobable.

Algunos ejemplos de código que probablemente serían difíciles de probar:

  • Una función 1000-LOC,
  • Código que depende en gran medida del estado global,
  • El código que requiere concreto, complica la creación de objetos, como el contexto de la base de datos, en lugar de depender de las interfaces y la inyección de dependencias,
  • Código que funciona lentamente ,
  • Código de espagueti,
  • Código heredado que se modificó durante años sin preocuparse por la legibilidad o la facilidad de mantenimiento,
  • Código difícil de entender que no tiene comentarios o sugerencias sobre la intención original del autor (por ejemplo, código que usa nombres de variables como function pGetDp_U(int i, int i2, string sText).

Tenga en cuenta que la falta de una arquitectura clara no hace que el código sea difícil de probar, ya que las pruebas unitarias se refieren a pequeñas partes del código. La arquitectura poco clara aún tendría un impacto negativo en la integración y las pruebas del sistema.

Arseni Mourzenko
fuente
8
También es difícil probar el código que no inyecta dependencias en funciones no puras, como números aleatorios, hora actual, E / S cableadas, etc.
9000
es trivial probar un código como ese: solo necesita las herramientas de prueba correctas, no alterar su código para adaptarlo. Pruebe Microsoft Fakes como ejemplo.
gbjbaanb
@MainMa, me gusta esta respuesta. ¿También estaría dispuesto a comentar un poco sobre qué factores impulsan las diferentes pruebas para la integración y las pruebas del sistema? Sé que la razón por la que he hecho preguntas similares a las de aquí en el pasado es porque no tenía una hoja de ruta que explicara qué tipos de pruebas se colocan mejor (o quizás, dónde se ubican de manera más rentable). las pruebas unitarias fueron las únicas.
J Trana
14

Hay muchas cosas que dificultan la prueba unitaria del código. Casualmente, muchos de ellos también dificultan el mantenimiento del código:

  • Ley de infracciones de Demeter .
  • Crear objetos dentro de un método en lugar de inyectar dependencias .
  • Acoplamiento apretado.
  • Mala cohesión.
  • Se basa en gran medida en los efectos secundarios.
  • Se basa en gran medida en globales o singletons.
  • No expone muchos resultados intermedios. (Una vez tuve que probar una función matemática de diez páginas con una sola salida y sin resultados intermedios disponibles. Mis predecesores básicamente codificaron cualquier respuesta que el código me diera).
  • Depende en gran medida y directamente de los servicios que son difíciles de burlar, como las bases de datos.
  • El entorno de tiempo de ejecución es significativamente diferente del entorno de desarrollo, como un objetivo incrustado.
  • Las unidades solo están disponibles en forma compilada (como una DLL de terceros).
Karl Bielefeldt
fuente
Creo que esta es una excelente respuesta. Tocas muchos problemas de nivel de código y problemas de estado global. @MainMa tiene otros problemas que creo que son válidos, pero están menos definidos. Jeffery Thomas menciona E / S y UI. Creo que si agrega las partes buenas de estas tres respuestas, tendría una excelente respuesta coherente. Sin embargo, me gusta más esta respuesta debido al enfoque en los antipatrones de código.
M2tM
1
Argh: nada peor que las afirmaciones de prueba de unidad que no tienen semejanza con los requisitos comerciales y que son solo la salida en un momento dado, ¿simulaciones que se configuran para llamarse 3 veces, por ejemplo? ¿Por qué 3? Porque eran las 3 la primera vez que se ejecutó la prueba :)
Michael
El acoplamiento apretado solo es malo cuando es inapropiado. El acoplamiento apretado en el código que es altamente cohesivo es una necesidad. Por ejemplo, una declaración de variable seguida de su uso. Bien acoplado, altamente cohesivo.
dietbuddha
1
Las pruebas unitarias donde el resultado se compara con lo que hizo el código, sin ningún caso de negocio / justificación, se llaman Pruebas de Caracterización. Se usan en el mantenimiento donde anteriormente no había pruebas y, a menudo, no había requisitos documentados y tiene que poner algo que se rompa si cambia la salida de esa función. Son mejores que nada.
Andy Krouwel
5

Ejemplos comunes de código que las personas no desean realizar pruebas unitarias:

  • Código que interactúa directamente con E / S (lectura de archivos, llamadas directas a la red, ...).
  • Código que actualiza directamente la interfaz de usuario.
  • Código que hace referencia directamente a objetos únicos u objetos globales.
  • Código que cambia implícitamente el estado del objeto o subobjeto.

Usando un marco simulado, todos estos ejemplos pueden ser probados en unidades. Es solo trabajo configurar los reemplazos simulados para las dependencias internas.

Cosas que realmente no pueden ser probadas:

  • Bucles infinitos (para un administrador de hilos, controlador o algún otro tipo de código de ejecución larga)
  • Ciertos tipos de operaciones de ensamblaje directo (que admiten algunos idiomas)
  • Código que requiere acceso privilegiado (no imposible, simplemente no es una buena idea)
Jeffery Thomas
fuente
2

Hay algunas áreas que pueden dificultar la escritura de pruebas unitarias. Sin embargo, quisiera enfatizar que eso no significa que deba descartar técnicas útiles directamente porque simplemente pueden agregar cierta complejidad a sus pruebas. Al igual que con cualquier codificación , debe hacer su propio análisis para determinar si los beneficios superan los costos, y no aceptar ciegamente lo que publica un tipo aleatorio en la red.

Mal escrito del código diseñado

  • acoplamiento inapropiado (generalmente acoplamiento apretado donde no debería estar)
  • código de fregadero de cocina (donde una función tiene demasiada lógica / responsabilidades)

La dependencia del estado en un ámbito diferente

El costo para la mayoría de estas espirales está fuera de control a menos que sepa qué está haciendo. Desafortunadamente, muchos a menudo no saben cómo usar estas técnicas para mitigar cosas como la complejidad de las pruebas.

  • Singletons
  • Globales
  • Cierres

Externo / Estado del sistema

  • Dependencias de hardware / dispositivo
  • Dependencias de red
  • Dependencias del sistema de archivos
  • Dependencias entre procesos
  • Otras dependencias de llamadas al sistema

Concurrencia

  • Roscado (cerraduras, secciones críticas, etc.)
  • tenedor
  • Corutinas
  • Llamadas de respaldo
  • Controladores de señal (no todos, pero algunos)
dietbuddha
fuente
2

No existe un código que no pueda probarse. Sin embargo, hay algunos ejemplos de código que es REALMENTE, REALMENTE difícil de probar (hasta el punto de que posiblemente no valga la pena):

Interacciones de hardware: si el código manipula directamente el hardware (por ejemplo, escribir en un registro para mover un dispositivo físico), la prueba de la unidad puede ser demasiado difícil o costosa. Si usa hardware real para la prueba, eso puede ser costoso para obtener retroalimentación apropiada en el arnés de prueba (¡aún más equipo!), Y si no lo hace, debe emular el comportamiento exacto de los objetos físicos, no es un truco pequeño. algunas instancias

Interacciones del reloj: esto suele ser más fácil, porque casi siempre es posible burlarse de las funciones del reloj del sistema de manera bastante trivial. Pero cuando no puede hacerlo, estas pruebas se vuelven inmanejables: las pruebas que se basan en tiempo real tienden a tardar mucho tiempo en ejecutarse y, en mi experiencia, tienden a ser muy frágiles ya que las cargas del sistema hacen que las cosas tarden más de lo debido , causando fallas en la prueba fantasma.

Michael Kohne
fuente
0

Mis tres grupos principales para esto son:

  • código que depende de servicios externos

  • sistemas que no permiten que los evaluadores modifiquen el estado independientemente de la aplicación.

  • entornos de prueba que no replican la configuración de producción.

Esto es lo que más he experimentado como desarrollador convertido en ingeniero de control de calidad.

Michael Durrant
fuente