¿Cómo escribir "buenas" pruebas de unidad?

61

Desencadenado por este hilo , (nuevamente) estoy pensando en usar finalmente pruebas unitarias en mis proyectos. Unos cuantos carteles dicen algo así como "Las pruebas son geniales, si son buenas pruebas". Mi pregunta ahora: ¿Qué son las "buenas" pruebas?

En mis aplicaciones, la parte principal a menudo es algún tipo de análisis numérico, que depende de las grandes cantidades de datos observados, y resulta en una función de ajuste que puede usarse para modelar estos datos. Me resultó especialmente difícil construir pruebas para estos métodos, ya que la cantidad de entradas y resultados posibles es demasiado grande para probar cada caso, y los métodos en sí son a menudo bastante largos y no se pueden volver a diseñar fácilmente sin sacrificar el rendimiento. Estoy especialmente interesado en las "buenas" pruebas para este tipo de método.

    
pregunta Jens 24.11.2010 - 10:47

9 respuestas

53

El arte de las pruebas unitarias dice lo siguiente sobre las pruebas unitarias:

  

Una prueba de unidad debe tener lo siguiente   propiedades:

     
  • Debe ser automatizado y repetible.
  •   
  • Debe ser fácil de implementar.
  •   
  • Una vez que esté escrito, debería permanecer para uso futuro.
  •   
  • Cualquiera debería poder ejecutarlo.
  •   
  • Debería ejecutarse con solo presionar un botón.
  •   
  • Debería ejecutarse rápidamente.
  •   

y luego agrega que debe ser completamente automatizado, confiable, legible y mantenible.

Recomendaría encarecidamente leer este libro si aún no lo has hecho.

En mi opinión, todos estos son muy importantes, pero los tres últimos (confiables, legibles y mantenibles) especialmente, como si sus pruebas tuvieran estas tres propiedades, entonces su código también las tiene.

    
respondido por el Andy Lowry 24.11.2010 - 14:58
42

Una buena prueba unitaria no refleja la función que está probando.

Como ejemplo muy simplificado, considera que tienes una función que devuelve un promedio de dos int. La prueba más completa llamaría a la función y verificaría si un resultado es en realidad un promedio. Esto no tiene ningún sentido en absoluto: está duplicando (replicando) la funcionalidad que está probando. Si cometió un error en la función principal, cometerá el mismo error en la prueba.

En otras palabras, si te encuentras replicando la funcionalidad principal en la prueba de la unidad, es probable que estés perdiendo el tiempo.

    
respondido por el mojuba 24.11.2010 - 11:28
10

Las buenas pruebas unitarias son esencialmente las especificaciones en forma ejecutable:

  1. describa el comportamiento del código correspondiente a los casos de uso
  2. cubrir casos de esquina técnicos (qué sucede si se pasa nulo): si no hay una prueba para un caso de esquina, el comportamiento no está definido.
  3. rompe si el código probado se aleja de la especificación

He descubierto que el desarrollo guiado por pruebas es muy adecuado para las rutinas de la biblioteca, ya que esencialmente escribes primero la API y ENTONCES la implementación real.

    
respondido por el user1249 24.11.2010 - 11:23
7

para TDD, las pruebas "buenas" prueban las características que el cliente desea ; las características no se corresponden necesariamente con las funciones, y el desarrollador no debe crear escenarios de prueba en un vacío

en su caso, supongo, la 'característica' es que la función de ajuste modela los datos de entrada dentro de una cierta tolerancia de error. Como no tengo idea de lo que realmente estás haciendo, estoy inventando algo; esperemos que sea análogo.

Ejemplo de historia:

Como [Piloto de X-Wing] quiero [no más de 0.0001% de error de ajuste] para que [el equipo que ataca pueda alcanzar el puerto de escape de la Estrella de la Muerte cuando se mueva a toda velocidad a través de un cañón de caja]

Así que vas a hablar con los pilotos (y con la computadora de destino, si es sensible). Primero hablas de lo que es 'normal', luego hablas de lo anormal. Averigua lo que realmente importa en este escenario, lo que es común, lo que es improbable y lo que es meramente posible.

Digamos que normalmente tendrá una ventana de medio segundo en siete canales de datos de telemetría: velocidad, inclinación, balanceo, giro, vector objetivo, tamaño objetivo y velocidad objetivo, y que estos valores serán constantes o cambiantes linealmente Anormalmente puede tener menos canales y / o los valores pueden estar cambiando rápidamente. Entonces, juntos se te ocurren algunas pruebas como:

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

Ahora, es posible que haya notado que no hay un escenario para la situación particular descrita en la historia. Resulta que, después de hablar con el cliente y otras partes interesadas, ese objetivo en la historia original fue solo un ejemplo hipotético. Las pruebas reales salieron de la discusión subsiguiente. Esto puede suceder. La historia debe ser reescrita, pero no tiene que ser [ya que la historia es solo un marcador de posición para una conversación con el cliente].

    
respondido por el Steven A. Lowe 24.11.2010 - 16:28
5

Cree pruebas para casos de esquina, como un conjunto de pruebas que contiene solo el número mínimo de entradas (1 o 0) y algunos casos estándar. Esas pruebas unitarias no reemplazan las pruebas de aceptación exhaustivas, ni deberían serlo.

    
respondido por el user281377 24.11.2010 - 11:00
5

He visto muchos casos en los que las personas invierten una enorme cantidad de esfuerzo escribiendo pruebas para el código que rara vez se ingresa y no escribiendo pruebas para el código que se ingresa con frecuencia.

Antes de sentarse a escribir cualquier prueba, debe mirar algún tipo de gráfico de llamadas para asegurarse de que planifica la cobertura adecuada.

Además, no creo en escribir exámenes solo por decir "Sí, lo probamos". Si utilizo una biblioteca que se deja caer y permanecerá inmutable, no voy a perder un día escribiendo pruebas para asegurar que los aspectos internos de una API que nunca cambiará funcionen como se espera, incluso si ciertas partes de la misma califican Alto en un gráfico de llamadas. Las pruebas que consumen dijo que la biblioteca (mi propio código) lo señala.

    
respondido por el Tim Post 24.11.2010 - 11:49
4

No es tan TDD, pero después de haber alcanzado el control de calidad, puede mejorar sus pruebas configurando casos de prueba para reproducir cualquier error que surja durante el proceso de control de calidad. Esto puede ser particularmente valioso cuando se trata de un soporte a largo plazo y empiezas a llegar a un lugar donde te arriesgas a personas que vuelven a introducir de forma involuntaria insectos antiguos. Tener una prueba para capturar es particularmente valioso.

    
respondido por el glenatron 24.11.2010 - 12:09
3

Intento que cada prueba solo pruebe una cosa. Intento darle a cada prueba un nombre como shouldDoSomething (). Intento probar el comportamiento, no la implementación. Sólo pruebo métodos públicos.

Por lo general, tengo una o unas pocas pruebas para el éxito, y luego tal vez un puñado de pruebas para el fracaso, según el método público.

Uso mucho las maquetas. Un buen marco simulado probablemente sería muy útil, como PowerMock. Aunque todavía no estoy usando ninguno.

Si la clase A usa otra clase B, agregaría una interfaz, X, para que A no use B directamente. Luego crearía una maqueta XMockup y la utilizaría en lugar de B en mis pruebas. Realmente ayuda a acelerar la ejecución de la prueba, reduce la complejidad de la prueba y también reduce la cantidad de pruebas que escribo para A, ya que no tengo que hacer frente a las peculiaridades de B. Por ejemplo, puedo probar que A llama a X.someMethod () en lugar de un efecto secundario de llamar a B.someMethod ().

También mantén limpio el código de prueba.

Al usar una API, como una capa de base de datos, me burlaría y permitiría que la maqueta arrojara una excepción en cada oportunidad posible en el comando. Luego ejecuto las pruebas una sin lanzar, y luego en un bucle, cada vez que lanzo una excepción en la próxima oportunidad hasta que la prueba tenga éxito nuevamente. Un poco como las pruebas de memoria disponibles para Symbian.

    
respondido por el Roger C S Wernersson 24.11.2010 - 12:43
2

Veo que Andry Lowry ya ha publicado las métricas de prueba de unidad de Roy Osherove; pero parece que nadie ha presentado el conjunto (complementario) que el tío Bob entrega en Clean Code (132-133). Él usa el acrónimo PRIMERO (aquí con mis resúmenes):

  • Rápido (deberían ejecutarse rápidamente, para que a las personas no les importe ejecutarlos)
  • Independiente (las pruebas no deben realizarse la configuración ni el desmontaje)
  • Repetible (debe ejecutarse en todos los entornos / plataformas)
  • Auto validación (totalmente automatizada; la salida debe ser "pasar" o "fallar", no un archivo de registro)
  • Oportunamente (cuándo escribirlos, justo antes de escribir el código de producción que prueban)
respondido por el Kazark 29.03.2013 - 18:39

Lea otras preguntas en las etiquetas