2011-03-24 18 views
5

Quiero encontrar una buena forma de generar vectores de prueba automáticamente. A modo de ejemplo, estoy probando un módulo de procesamiento de audio llamando a una función que ejercita el módulo bajo prueba con el vector de prueba especificado y, al hacerlo, realiza varias comprobaciones para el funcionamiento correcto y la corrección de la salida del módulo.Generación automática/plantilla de vectores de prueba en C++

void runTest(const char *source, double gain, int level); 

El vector de prueba es el triplete de source, gain y level. Aquí está el espacio multidimensional Quiero poner a prueba en contra:

const char *sources[] = {"guitar.mp3", "vocals.mp3", "drums.mp3"}; 
double gains[] = {1., 10., 100.}; 
int levels[] = {1, 2, 3, 4}; 

valores pueden tener otras propiedades, por ejemplo, si tiene una rabia vocals.mp3 dinámica de 2, 5 la guitarra y la batería 10, podríamos concebir una representación como:

int dynamicRange(const char *source); 

Quiero ser capaz de configurar varias pruebas. Por ejemplo, quiero ser capaz de ejecutar:

// all permutations (total 36 vectors) 
runTest("guitar.mp3", 1., 1); 
runTest("guitar.mp3", 1., 2); 
runTest("guitar.mp3", 1., 3); 
runTest("guitar.mp3", 1., 4); 
runTest("guitar.mp3", 1., 1); 
runTest("guitar.mp3", 10., 2); 
runTest("guitar.mp3", 10., 3); 
// ... 

// corner cases (according to dynamicRange) 
runTest("vocals.mp3", 1., 1); 
runTest("vocals.mp3", 1., 4); 
runTest("vocals.mp3", 100., 1); 
runTest("vocals.mp3", 100., 4); 
runTest("drums.mp3", 1., 1); 
runTest("drums.mp3", 1., 4); 
runTest("drums.mp3", 100., 1); 
runTest("drums.mp3", 100., 4); 

// sparse/minimal tests touching every value for each parameter 
runTest("guitar.mp3", 1., 1); 
runTest("vocals.mp3", 10., 2); 
runTest("drums.mp3", 100., 3); 
runTest("guitar.mp3", 1., 4); 

// quick test 
runTest("guitar.mp3", 1., 1); 

Quiero crear el código anterior y sin un montón de copiar y pegar de forma dinámica o usando mi compilador para hacer el trabajo de campo, por ejemplo:

// syntax tentative here, could be class/template instantiations 
allPermutations(runTest, sources, gains, levels); 
cornerCases(runTest, lookup(sources, dynamicRange), gains, levels); 
minimal(runTest, sources, gains, levels); 
quick(runTest, sources, gains, levels); 

Lo anterior parece una C dinámica, pero mi lenguaje es C++ y espero utilizar plantillas y una combinación de técnicas dinámicas y estáticas. Tal vez incluso metaprogramación.

Las combinaciones y variaciones también serían interesantes. Por ejemplo, podría querer usar solo el archivo de entrada más corto. O puede que desee ejecutar todas las fuentes con corner-cases para gain y level. O gain también podría ser un rango continuo de 1 a 100, pero vamos a mantener las cosas discretas por ahora.

Antes de comenzar a diseñar tipos, plantillas, representación, etc. Me preguntaba si esto es un problema que se ha resuelto antes o, si no, las bibliotecas existentes, p. Boost MPL, se útil?

+0

¿Por qué quieres plantillas? ¿No están anidados para bucles lo suficiente? –

+0

No necesito necesariamente plantillas pero solo quiero escribir "allPermutations", "cornerCases", "minimal", "allPairs", etc. una vez para hacer frente a cualquier número de dimensiones y todos los tipos de parámetros. – paperjam

+0

Ok, me perdí esto. La mejor forma es, probablemente, utilizar una interfaz común basada en, por ejemplo. 'boost :: any' para pasar los parámetros de prueba. De esta forma, separa la asignación de parámetros y el vertido de las pruebas en sí. En este sentido, la solución de @Alexander Poluektov parece lo suficientemente flexible si no desea utilizar un marco particular. –

Respuesta

1

era muy tentador pensar un poco acerca de esta tarea muy fáciles de programar :)

Aquí me encontré con una solución dinámica utilizando cualquier impulso :: como el medio para almacenar tipos "borrados" en. Más estática la solución probablemente usaría Boost.Tuple y Boost.Fusion/Boost.MPL de hecho, pero no estoy seguro de que valga la pena.

El código es de calidad prototipo, y seguro que no vas a utilizarlo tal cual. Pero al menos puede darte dirección.

Así que el mini-marco:

typedef boost::option<boost::any> OptionalValue; 
OptionalValue const no_value; 

// represents each dimension from your multi-dimensional solution 
struct Emitter 
{ 
    virtual ~Emitter() { } 

    // should return no_value to indicate that emitting finished 
    virtual OptionalValue emit() = 0; 
}; 
typedef boost::shared_ptr<Emitter> EmitterPtr; 

// generates test vectors according to passed emitters and run test function on each 
class Generator 
{ 
public: 

    void add_emitter(EmitterPtr p) { emitters.push_back(p); } 

    // here f is callback called for each test vector 
    // could call test, or could store test vector in some container 
    template <class F> 
    void run(F f) 
    { 
     std::vector<boost::any> v; 
     generate(v, 0, f); 
    } 

private: 

    template <class F> 
    void generate(vector<boost::any>& v, size_t i, F f) 
    { 
     if (i == emitters.size()) 
     { 
      f(v); 
     } 

     EmitterPtr e = emitters[i]; 
     for (OptionalValue val = e->emit(); val;) 
     { 
      v.push_back(*val); 
      generate(v, i + 1, f); 
      v.pop_back(); 
     } 
    } 

private: 
    std::vector<EmitterPtr> emitters; 
}; 

Algunos emisores concretos:

// emits all values from given range 
template <class FwdIt> 
struct EmitAll : Emitter 
{ 
    EmitAll(FwdIt begin, FwdIt end) : current(begin), end(end) { } 
    OptionalValue emit() { return current == end ? no_value : *(current++); } 

    FwdIt current; 
    FwdIt const end; 
}; 

// emits first value from given range, and finshes work 
template <class FwdIt> 
struct EmitFirst : Emitter 
{ 
    EmitFirst(FwdIt begin, FwdIt) : current(begin), n(0) { } 
    OptionalValue emit() { return n++ == 0 ? *current : no_value; } 

    FwdIt current; 
    size_t n; 
}; 

// emits only values satisfied predicate P 
template <class FwdIt, class P> 
struct EmitFiltered 
{ 
    EmitFiltered(FwdIt begin, FwdIt end) : current(begin), end(end) { } 
    OptionalValue emit() 
    { 
     P const p; 
     while (current != end) 
     { 
      if (!p(current)) continue; 
      return *(current++); 
     } 
     return no_value; 
    } 

    FwdIt current; 
    FwdIt const end; 
}; 

// helpers for automatic types' deducing 
template <class FwdIt> 
EmitterPtr make_emit_all(FwdIt b, Fwd e) { return new EmitAll<FwdIt>(b, e); } 

template <class FwdIt> 
EmitterPtr make_emit_first(FwdIt b, Fwd e) { return EmitFirst<FwdIt>(b, e); } 

template <class FwdIt> 
EmitterPtr make_emit_filtered(FwdIt b, Fwd e, P p) { return EmitFiltered<FwdIt, P>(b, e, p); } 

Adaptador para runTest:

struct Run 
{ 
    void operator()(const std::vector<boost::any>& v) 
    { 
     assert v.size() == 3; 
     runTest(boost::any_cast<std::string>(v[0]), 
       boost::any_cast<double>  (v[1]), 
       boost::any_cast<int>  (v[2])); 
    } 
}; 

Por último uso:

Generator all_permutations; 
all_permutations.add_emitter(make_emit_all(sources, sources + 3)); 
all_permutations.add_emitter(make_emit_all(gains, gains + 3)); 
all_permutations.add_emitter(make_emit_all(levels, levels + 4)); 

Generator quick; 
quick.add_emitter(make_emit_first(sources, sources + 3)); 
quick.add_emitter(make_emit_first(gains, gains + 3)); 
quick.add_emitter(make_emit_first(levels, levels + 4)); 

Generator corner_cases; 
corner_cases.add_emitter(make_emit_all(sources, sources + 3)); 
corner_cases.add_emitter(make_emit_filtered(gains, gains + 3, LookupDynamicRange)); 
corner_cases.add_emitter(make_emit_all(levels, levels + 4)); 

Run r; 
all_permutations.run(r); 
quick.run(r); 
corner_cases(r); 

La implementación de todos los pares de bestia (para el individuo 'mínimo') se deja a usted para implementar%)

+0

Se ve bien, ¡gracias !. Lo intentaré más tarde. No he usado boost :: any antes. – paperjam

+0

Gracias por marcar mi respuesta. Pero sé que creo que tener el concepto de "iterador" en lugar de "emitter" sería más apropiado: necesitas el método 'reset()' en la clase Emitter de todos modos, ¿por qué no simplemente dar buenos conceptos y operaciones a los conceptos y operaciones (iterator, begin, fin, ++). De todos modos, depuré el código que había publicado, así que si le interesan las correcciones, coloque una línea aquí. –

+0

Estoy de acuerdo en que el iterador tiene más sentido y, después de haber profundizado en esto, quizás incluso el concepto de "rango" de Boost sea mejor. Esto permite operaciones de rango en los conjuntos de datos de entrada, lo cual es bastante poderoso y significa que solo necesito un puñado de "generadores" que operen en el espacio multidimensional: exhaustivos, de todos los valores y de todos los pares. – paperjam

3

Creo que sería útil si te presentas al concepto de All-pairs testing, y tienes una comprobación rápida para QuickCheck (es el marco de prueba Haskell que genera casos de prueba aleatoriamente según la especificación dada, y luego verifica que algunas propiedades son hold; existe C++ version of it).

En lo que respecta a Boost.MPL en particular, no creo que le ayude en absoluto en esta tarea: no se trata de una lista de tipos aquí, ¿verdad?

Mi otro consejo sobre su próximo diseño: no sobregeneralice. Antes de comenzar con tipos, plantillas, etc. implemente 3 (tres) implementaciones razonablemente diferentes, y luego generalice lo que ya tiene a mano.

+0

Re. lista de tipos - eso es exactamente con lo que estoy tratando, ¿no? Cada parámetro podría ser de un tipo diferente y podría haber cualquier número de ellos. La prueba de todos los pares parece un arma interesante para agregar a Exhaustive, Corner-cases, etc. – paperjam

+0

Prefiero usar aquí una solución más dinámica que la lista de tipos. Publicaría algún código pronto. –

+0

+1 para todos los pares. – mskfisher

2

que podría estar interesado en Template2Code marco. Está especialmente diseñado para resolver su problema. La documentación completa es here. De acuerdo con la documentación se debe crear un *.t2c file de la siguiente estructura para generar un conjunto completo de vectores de prueba:

<BLOCK> 
    ... 
    <DEFINE> 
     #define SOURCE <%0%> 
     #define GAIN <%1%> 
     #define LEVEL <%2%> 
    </DEFINE> 
    <CODE> 
     runTest(SOURCES, GAINS, LEVELS); 
    </CODE> 
    <VALUES> 
     SET("guitar.mp3"; "vocals.mp3"; "drums.mp3") 
     SET(1.; 10.; 100.) 
     SET(1; 2; 3; 4) 
    </VALUES> 
    ... 
</BLOCK> 

Esta tecnología fue utilizada por The Linux Foundation y ISPRAS para crear "normal"-quality tests para libstdcxx, simplista, GTK, fontconfig, freetype y otras bibliotecas.