¿Cómo escribir pruebas que tengan sentido para el software de visualización?

8

Tengo un software bastante grande que toma ciertos tipos de archivos y los visualiza / crea una gran cantidad de botones para la manipulación de la imagen trazada. Siento que estoy encontrando errores / piezas de código que en realidad no funcionan una vez a la semana, pero me cuesta entender cómo puedo escribir pruebas para este software.

Entiendo cómo las pruebas son importantes para proyectos como bibliotecas y API, simplemente escribe pruebas que usan estas funciones.

¿Pero qué pasa con el software de visualización? Parece requerir un enfoque diferente debido a los elementos visuales involucrados.

¿Necesito escribir un programa de prueba o un arnés de prueba que se ejecute y llame manualmente a todas las operaciones, siempre que pueda usarlas en los datos?

¿Qué enfoque debo usar para comenzar a escribir pruebas para validar que reparé los errores y alertarme si el código se rompe nuevamente?


Hay una pregunta relacionada pero no duplicada sobre cuándo debe realizar la prueba unitaria. Como estoy descubriendo errores, quiero escribir pruebas para ayudar a evitar que el software retroceda nuevamente.

Need4Sleep
fuente

Respuestas:

8

Hay un par de cosas que puede hacer para hacer que las pruebas de software sean más fáciles. Primero, intente abstraer todo lo que pueda en capas que no sean visuales. Eso le permitirá escribir pruebas unitarias estándar en esas capas inferiores. Por ejemplo, si tiene un botón que realiza un cierto cálculo, asegúrese de tener una manera de realizar ese cálculo en una prueba unitaria con una llamada de función regular.

La otra sugerencia para probar programas con muchos gráficos es crear una salida que un probador pueda verificar fácilmente de forma manual. Un ejemplo de Minecraft es:

salida de prueba de minecraft

También trabajé en proyectos que tenían un montón de pruebas que mostraban algo en la pantalla, luego le pedí al probador que verificara manualmente si coincidía con la descripción. Esto asegura que no olvide los casos de prueba más tarde.

Las pruebas son a menudo extremadamente difíciles si no diseñaste originalmente teniendo en cuenta las pruebas. Solo trabaje primero para poner a prueba su código más frágil. Cuando encuentre un error, haga una prueba que detecte ese error si vuelve a ocurrir. Eso a menudo provoca escribir pruebas relacionadas mientras lo haces.

Karl Bielefeldt
fuente
2
+1 y si los datos de prueba muestran una imagen estática, verifique manualmente el resultado la primera vez y luego guarde la captura de pantalla para la comparación programática a partir de entonces
Steven A. Lowe
6

Todo tiene una interfaz. Cuando me pongo el sombrero de prueba, uso una visión del mundo específica para escribir una prueba:

  • Si algo existe, se puede medir.
  • Si no se puede medir, no importa. Si importa, simplemente no he encontrado una manera de medirlo todavía.
  • Los requisitos prescriben propiedades medibles, o son inútiles.
  • Un sistema cumple un requisito cuando pasa de un estado no esperado al estado esperado prescrito por el requisito.
  • Un sistema consta de componentes que interactúan, que pueden ser subsistemas. Un sistema es correcto cuando todos los componentes son correctos y la interacción entre los componentes es correcta.

En su caso, su sistema tiene tres partes principales:

  • algún tipo de datos o imágenes, que se pueden inicializar desde archivos
  • un mecanismo para mostrar los datos
  • un mecanismo para modificar los datos

Por cierto, eso me parece mucho la arquitectura original del Modelo-Vista-Controlador. Idealmente, estos tres elementos exhiben un acoplamiento flojo, es decir, usted define límites claros entre ellos con interfaces bien definidas (y por lo tanto bien comprobables).

Una interacción compleja con el software puede traducirse en pequeños pasos que pueden expresarse en términos de los elementos del sistema que estamos probando. Por ejemplo:

Cargo un archivo con algunos datos. Muestra un gráfico. Cuando arrastro un control deslizante en la interfaz de usuario, el gráfico se vuelve tambaleante.

Esto parece ser fácil de probar manualmente y difícil de probar automatizado. Pero traduzcamos esa historia a nuestro sistema:

  • La interfaz de usuario proporciona un mecanismo para abrir un archivo: el controlador es correcto.
  • Cuando abro un archivo, el Controlador emite un comando apropiado para el Modelo: la interacción Controlador-Modelo es correcta.
  • Dado un archivo de prueba, el modelo analiza esto en la estructura de datos esperada: el modelo es correcto.
  • Dada una estructura de datos de prueba, la Vista representa el resultado esperado: la Vista es correcta. Algunas estructuras de datos de prueba serán gráficas normales, otras serán gráficas tambaleantes.
  • La interacción Ver – Modelo es correcta
  • La interfaz de usuario proporciona un control deslizante para hacer que el gráfico se tambalee: el controlador es correcto.
  • Cuando el control deslizante se establece en un valor específico, el Controlador emite el comando esperado al Modelo: la interacción Controlador-Modelo es correcta.
  • Al recibir un comando de prueba con respecto a la oscilación, el Modelo transforma una estructura de datos de prueba en la estructura de datos de resultados esperados.

Agrupados por componente, terminamos con las siguientes propiedades para probar:

  • Modelo:
    • analiza archivos
    • responde al comando de abrir archivo
    • proporciona acceso a los datos
    • responde al comando make-wobbly
  • Ver:
    • procesa datos
  • Controlador:
    • proporciona flujo de trabajo de archivo abierto
    • emite el comando de abrir archivo
    • proporciona flujo de trabajo improvisable
    • emite el comando make-wobbly
  • sistema completo:
    • La conexión entre los componentes es correcta.

Si no descomponemos el problema de las pruebas en subpruebas más pequeñas, las pruebas se vuelven realmente difíciles y realmente frágiles. La historia anterior también podría implementarse como "cuando cargo un archivo específico y establezco el control deslizante en un valor específico, se representa una imagen específica". Esto es frágil ya que se rompe cuando cambia cualquier elemento del sistema.

  • Se rompe cuando cambio los controles de oscilación (p. Ej., Maneja en el gráfico en lugar de un control deslizante en un panel de control).
  • Se rompe cuando cambio el formato de salida (por ejemplo, el mapa de bits representado es diferente porque cambié el color predeterminado del gráfico o porque agregué suavizado para que el gráfico se vea más uniforme. Tenga en cuenta que en ambos casos).

Las pruebas granulares también tienen la gran ventaja de que me permiten evolucionar el sistema sin temor a romper ninguna característica. Dado que todo el comportamiento requerido se mide mediante un conjunto de pruebas completo, las pruebas me notificarán si algo se rompe. Como son granulares, me señalarán el área del problema. Por ejemplo, si cambio accidentalmente la interfaz de cualquier componente, solo las pruebas de esa interfaz fallarán y no cualquier otra prueba que utilice indirectamente esa interfaz.

Si se supone que la prueba es fácil, esto requiere un diseño adecuado. Por ejemplo, es problemático cuando cableo componentes en un sistema: si quiero probar la interacción de un componente con otros componentes en un sistema, necesito reemplazar esos otros componentes con trozos de prueba que me permiten iniciar sesión, verificar, y coreografiar esa interacción. En otras palabras, necesito algún mecanismo de inyección de dependencia, y se deben evitar las dependencias estáticas. Al probar una interfaz de usuario, es de gran ayuda cuando esta interfaz de usuario es programable.


Por supuesto, la mayor parte de eso es solo una fantasía de un mundo ideal donde todo está desacoplado y fácilmente comprobable y los unicornios voladores transmiten amor y paz ;-) Si bien cualquier cosa es fundamentalmente comprobable, a menudo es prohibitivamente difícil hacerlo, y es mejor hacerlo usos de tu tiempo. Sin embargo, los sistemas se pueden diseñar para que sean comprobables y, por lo general, incluso los sistemas independientes de las pruebas cuentan con API o contratos internos que se pueden probar (si no, apuesto a que su arquitectura es una mierda y ha escrito una gran bola de barro). En mi experiencia, incluso pequeñas cantidades de pruebas (automatizadas) producen un aumento notable de la calidad.

amon
fuente
2

Comience con un archivo conocido que produzca una imagen esperada. Verifica cada píxel. Cada uno debe tener un valor esperado para un archivo de prueba conocido, hecho a mano. Debería tener una imagen de salida esperada para compararla. Cualquier cosa que esté "desactivada" significa un error en su código.

Expanda su archivo de prueba para que la imagen de salida cambie y llegue a todas las funciones de su software.

Las secuencias de comandos serían útiles para ese tipo de pruebas de caja negra. Un script simple que ejecuta la última versión de su software para la entrada conocida y la salida esperada.

Las pruebas unitarias, por otro lado, deben ser pruebas de caja blanca donde se toma la menor cantidad posible de software, generalmente una función o lo que sea, y ver si se comporta como espera. Podrías ver qué color de píxel devuelve, o lo que sea. En este caso, su código se comporta como una biblioteca, con API para todas las demás secciones de su código.

Si todo está guardado en un archivo .c con toda la funcionalidad incorporada main(), entonces tienes problemas más grandes que la forma de probar.

Philip
fuente