2011-10-06 11 views
11

He empezado a utilizar googletest para implementar pruebas y tropezamos con esta cita en la documentación relativa a value-parameterized tests¿Las pruebas basadas en datos son malas?

  • ¿Quieres probar el código a través de varias entradas (también conocido como las pruebas según los datos). Esta función es fácil de abusar, así que ¡por favor ejercite su buen sentido al hacerlo!

Creo que estoy hecho "abusar" del sistema cuando se hace lo siguiente y le gustaría conocer sus opiniones y comentarios sobre este asunto.

Asumamos que tenemos el siguiente código:

template<typename T> 
struct SumMethod { 
    T op(T x, T y) { return x + y; } 
}; 

// optimized function to handle different input array sizes 
// in the most efficient way 
template<typename T, class Method> 
T f(T input[], int size) { 
    Method m; 
    T result = (T) 0; 
    if(size <= 128) { 
     // use m.op() to compute result etc. 
     return result; 
    } 
    if(size <= 256) { 
     // use m.op() to compute result etc. 
     return result; 
    } 
    // ... 
} 

// naive and correct, but slow alternative implementation of f() 
template<typename T, class Method> 
T f_alt(T input[], int size); 

Ok, así que con este código, sin duda tiene sentido para probar f() (por comparación con f_alt()) con diferentes tamaños de matriz de entrada de datos generados aleatoriamente para poner a prueba la la corrección de las ramas. Además de eso, tengo varios structs como SumMethod, MultiplyMethod, etc, así que me estoy quedando un número bastante grande de pruebas también para diferentes tipos:

typedef MultiplyMethod<int> MultInt; 
typedef SumMethod<int> SumInt; 
typedef MultiplyMethod<float> MultFlt; 
// ... 
ASSERT(f<int, MultInt>(int_in, 128), f_alt<int, MultInt>(int_in, 128)); 
ASSERT(f<int, MultInt>(int_in, 256), f_alt<int, MultInt>(int_in, 256)); 
// ... 
ASSERT(f<int, SumInt>(int_in, 128), f_alt<int, SumInt>(int_in, 128)); 
ASSERT(f<int, SumInt>(int_in, 256), f_alt<int, SumInt>(int_in, 256)); 
// ... 
const float ep = 1e-6; 
ASSERT_NEAR(f<float, MultFlt>(flt_in, 128), f_alt<float, MultFlt>(flt_in, 128), ep); 
ASSERT_NEAR(f<float, MultFlt>(flt_in, 256), f_alt<float, MultFlt>(flt_in, 256), ep); 
// ... 

Ahora, por supuesto, mi pregunta es: ¿Esto hace cualquier sentido y por qué sería esto malo?

De hecho, he encontrado un "error" al ejecutar pruebas con float s donde f() y f_alt() daría valores diferentes con SumMethod debido al redondeo, lo que podría mejorar mediante la preclasificación la matriz de entrada etc .. A partir de esta experiencia Considero que en realidad esta es una buena práctica.

Respuesta

10

Creo que el problema principal es probar con "datos generados aleatoriamente". No queda claro a partir de su pregunta si estos datos se vuelven a generar cada vez que se ejecuta el arnés de prueba. Si es así, entonces los resultados de su prueba no son reproducibles. Si alguna prueba falla, debería fallar cada vez que la ejecutes, no una vez en una luna azul, con alguna extraña combinación de datos de prueba aleatoria.

Por lo tanto, en mi opinión, debe pregenerar los datos de prueba y mantenerlos como parte de su conjunto de pruebas. También debe asegurarse de que el conjunto de datos sea lo suficientemente grande y diverso como para ofrecer una cobertura de código suficiente.

Además, como Ben Voigt comentó a continuación, la prueba con datos aleatorios solo no es suficiente. Debe identificar casos de esquina en sus algoritmos y probarlos por separado, con datos adaptados específicamente para estos casos. Sin embargo, en mi opinión, las pruebas adicionales con datos aleatorios también son beneficiosas cuando/si no está seguro de conocer todos sus casos de esquina. Puede golpearlos por casualidad usando datos aleatorios.

+2

Los datos generados aleatoriamente son malos por dos razones: primero, porque como mencionaste, las pruebas no son reproducibles. Y en segundo lugar, porque los casos de esquina pueden no estar cubiertos por datos generados aleatoriamente. Guardar los vectores aleatorios no hace nada para el segundo inconveniente. –

+0

Gracias. Enmendé mi respuesta, tienes razón, por supuesto. – haimg

+0

@haimg: si haces una prueba de caja negra, ¿cómo conoces el algoritmo utilizado y sus casos de esquina? :-) –

6

El problema es que no se puede afirmar la corrección en los flotadores de la misma manera que lo hace con los ints.

Compruebe la corrección dentro de un cierto épsilon, que es una pequeña diferencia entre los valores calculados y los esperados. Eso es lo mejor que puedes hacer. Esto es cierto para todos los números de coma flotante.

Creo que estoy hecho "abusar" del sistema cuando se hace la siguiente

¿Pensaste que era mala antes de leer ese artículo? ¿Puedes articular lo que es malo sobre eso?

Tiene que probar esta funcionalidad en algún momento. Necesitas datos para hacerlo. ¿Dónde está el abuso?

+0

Sure. Me olvidé de ponerlo correctamente en el ejemplo anterior. Editado Aparte de eso, estoy más interesado en los argumentos en contra de escribir este tipo de pruebas. – bbtrb

0

Una de las razones por las que podría ser malo es que las pruebas controladas por datos son más difíciles de mantener y en un período de tiempo más largo es más fácil introducir errores en las pruebas. Para más detalles mira aquí: http://googletesting.blogspot.com/2008/09/tott-data-driven-traps.html

También desde mi punto de vista, los tests de unidad son los más útiles cuando estás haciendo una refactorización seria y no estás seguro de si no has cambiado la lógica de forma incorrecta. Si su prueba de datos aleatorios falla después de ese tipo de cambios, entonces puede adivinar: ¿es debido a los datos o debido a sus cambios?

Sin embargo, creo que podría ser útil (al igual que las pruebas de estrés que tampoco son 100% reproducibles). Pero si está utilizando algún sistema de integración continua, no estoy seguro si las pruebas basadas en datos con gran cantidad de datos generados aleatoriamente deberían incluirse en él. Prefiero hacer una implementación separada que periódicamente haga mucho de pruebas aleatorias a la vez (por lo que la probabilidad de descubrir algo malo debería ser bastante alta cada vez que la ejecuta). Pero es demasiado pesado como parte del conjunto de pruebas normales.

Cuestiones relacionadas