¿Cómo debo hacer una prueba unitaria de una clase que se basa en tener datos realistas?

8

Tengo una clase que encapsula los resultados de una medición científica. Estoy construyendo pruebas unitarias desde el principio, pero no tengo mucha experiencia con las pruebas unitarias y no estoy seguro de qué comportamientos debería probar y cómo.

Mi clase hace tres tipos de cosas:

  1. Lee datos de medición de un archivo (o una cadena) en sus variables de instancia
  2. Escribe sus datos de medición en un archivo o una cadena
  3. Realiza cálculos sobre sus datos (por ejemplo, obtener el promedio de un conjunto de números)

Mi enfoque en este momento es incluir un archivo de datos de ejemplo bien conocido en mi testdirectorio. Una prueba lee los datos del archivo, los pasa a mi clase y se asegura de que cumplan con algunos controles básicos de cordura. Otra prueba pasa el nombre del archivo a mi clase, deja que la clase lo lea y ejecuta las mismas pruebas. El resto de las pruebas leen los datos del archivo, los pasan a mi clase y verifican que los resultados de los métodos de procesamiento de datos sean correctos, dado lo que sé sobre ese conjunto de datos.

Sin embargo, esto parece bastante enredado. Las pruebas que verifican (3) suponen implícitamente que los comportamientos de (1) son correctos, ya que son las funciones en (1) las que se utilizan para llenar la clase en primer lugar. Y las pruebas de (1) podrían beneficiarse de las extensas comprobaciones realizadas por las pruebas de (3). ¿Estoy estructurando mal mis pruebas unitarias, o es solo un resultado natural del hecho de que necesito usar un conjunto de datos específico en mis pruebas?

Bdesham
fuente
2
Bienvenido a las pruebas de integración. En serio, estás en el camino correcto. Algunas consideraciones: si quiere que parezca que su proyecto se está haciendo rápidamente, intente la integración big-bang. Si usted quiere ser capaz de entender realmente lo que está mal con su programa, ahora y después, continuar estableciendo metódica casos de prueba para cada subsistema.
Andyz Smith

Respuestas:

14

Lo que está haciendo son pruebas de integración, porque como probablemente pueda ver, sus pruebas dependen de otras partes de su código. Esto está bien, pero es bueno saber cuándo estás mirando artículos / ejemplos / etc. en línea.

Algunos puntos a considerar y cosas para recordar:

  • Organizar , actuar , afirmar . Todas las pruebas tienen estos 3 pasos.
    • ¿Necesitas más pasos? Probablemente esté probando demasiado para 1 prueba
    • No organizar? Tiene dependencias externas, lo que casi siempre hace que las pruebas sean escamosas y poco confiables.
    • Un montón de código Arrange generalmente significa muchas dependencias y tipos de dependencias de estado del sistema. Esa es una receta para el código frágil.
    • No acto? A menudo prueba el compilador. Deje eso a los desarrolladores del compilador.
    • Un montón de acto (s)? Probablemente esté probando demasiado para 1 prueba nuevamente.
    • No afirmar? A menudo, el caso cuando desea probar que "no se produjeron errores". Sin errores! = Funciona correctamente.
    • ¿Muchos activos? El código bajo prueba podría estar haciendo demasiado / tocar demasiados sistemas. Intente refactorizar y probar los bits individuales en su lugar.
  • Las pruebas deben existir por sí mismas. Evite escenarios en los que algo que prueba 3 depende del código que hace 1 . En cambio, busque reemplazar el código que hace 1 con el código de prueba. En su escenario: intente configurar manualmente los valores a lo que desea que sean en el paso Organizar de la prueba , luego realice el ion Act (los cálculos) y luego afirme el resultado.
  • Las pruebas de ruta feliz a menudo son una pérdida de tiempo. Los mejores escenarios casi siempre funcionan. Sus pruebas deberían tratar de forzar errores y casos extremos.
    • ¿Tiene una prueba que pasa malas secuencias para ser procesadas?
    • ¿Tiene una prueba que pasa nombres de archivo nulos / no existentes para ser procesados?
    • ¿Qué sucede si los valores numéricos para calcular no son numéricos, o cuando se analizan son enormes , o producen valores enormes cuando se calculan?
  • Sorprendentemente, escribir pruebas rara vez confirma que lo que has hecho es correcto. En cambio, le dan una idea de cuán útil, estable y flexible es su diseño.

editar> Esto ya ha sido aceptado, pero quería agregar algo que aprendí hace mucho tiempo a través de Pragmatic Unit Testing :

Prueba de unidad con su BICEP correcto

  • ¿Son correctos los resultados ?
  • CORRECTO B Condiciones oundary
    • C onformar al formato esperado
    • O rdered correctamente
    • R ange es correcto
    • Las referencias a dependencias externas son seguras
    • E xistencia (nula, etc.)
    • C ardinalidad
    • T IMing (cosas suceden en el orden correcto, con la sincronización correcta)
  • I relaciones inversas
  • C ross-Compruebe los resultados con otros medios
  • Los errores son forzados
  • P características endimiento son dentro de límites aceptables
Steven Evers
fuente
5

Creo que su clase es difícil de evaluar porque tiene demasiadas responsabilidades. Los mencionas tú mismo

  • Leer datos del archivo
  • Escribir datos para archivar
  • Realizar el cálculo

Idealmente, una clase debería tener una sola responsabilidad o un área de responsabilidad única. En este caso, definitivamente debe tener una clase que contenga solo la lógica para realizar los cálculos.

Si tuviera eso, tal vez podría tener una función como esta:

CalculationResult PerformCalculation(Measurement[] measurements)
{ ... }

Esto se vuelve relativamente fácil de probar, ya que puede proporcionar fácilmente una serie de mediciones bien definidas en una prueba.

Supongo que aquí lees del archivo una vez para obtener todas las medidas a la vez. Si recopila nuevas medidas escritas en el archivo a lo largo del tiempo, su clase podría verse así:

class Calculator {
    AddMeasurement(Measurement measurement) { ... }

    CalculationResult PerformCalculation() { ... }
}

Todavía es fácil de probar, porque puede alimentar a la clase con una serie de mediciones bien definidas durante la prueba y leer el resultado, sin tener que depender del sistema de archivos.

Una clase diferente podría tener la responsabilidad de leer los datos de Medición del archivo. Esto podría dividirse nuevamente en leer datos de un archivo y analizar los datos como objetos de medición, para permitir probar la lógica de análisis por separado sin depender del sistema de archivos, etc.

Si es difícil escribir una prueba de unidad, esto a menudo es una señal de que su "unidad" tiene múltiples responsabilidades, demasiadas dependencias o, de otras formas, no es SÓLIDA .

Pete
fuente
3

Las pruebas unitarias deben probar dos conjuntos de casos:

Los casos muy básicos: es el cálculo correcto, suman los totales, etc. Utilice datos simples y fáciles de verificar para estos casos.

The edge cases: fecha de entrega del 29 de febrero de 2016, cantidad de pedido de 999,999, artículos con un precio de $ 0.00, GPS para el Polo Norte (¡trate de mudarse al oeste!), Etc., etc.

James Anderson
fuente
0

Creo que muchas de las respuestas son acertadas, en el sentido de que muchas de las pruebas esenciales parecen estar combinadas en una instancia de prueba unitaria. PERO.

Muchas funciones requieren datos complicados y producen resultados complicados. El procesamiento de imágenes, por ejemplo, puede tener una función compacta que produce una máscara a partir de una imagen. Un controlador de prueba (yo diría que es apropiado) leería una imagen, la procesaría, escribiría un archivo de imagen y compararía el archivo resultante con un archivo de imagen de referencia.

Probar la integración en el objetivo de toda esa funcionalidad sería una prueba de integración. Probar una sola función de su objetivo, una entrada complicada a una salida complicada, es una prueba unitaria adecuada.

Sasguy
fuente