Автоматическая / шаблонная генерация тестовых векторов в C ++ - PullRequest
5 голосов
/ 24 марта 2011

Я хочу найти хороший способ автоматически генерировать тестовые векторы.В качестве примера, я тестирую модуль обработки звука, вызывая функцию, которая выполняет тестируемый модуль с указанным тестовым вектором и при этом выполняет различные проверки правильности работы и правильности вывода модуля.

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

Тестовым вектором является триплет source, gain и level.Вот многомерное пространство, с которым я хочу протестировать:

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

Значения могут иметь другие свойства, например, если vocals.mp3 имеет динамическую ярость 2, гитару 5 и ударные 10, мы могли бы представить представлениекак:

int dynamicRange(const char *source);

Я хочу иметь возможность настраивать различные тестовые прогоны.Например, я хочу иметь возможность выполнить:

// 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);

Я хочу создать приведенный выше код без большого количества копий и вставлять либо динамически, либо используя мой компилятор для выполнения работы, например:

// 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);

Вышесказанное выглядит как динамический C, но мой язык - C ++, и я ожидаю использовать шаблоны и некоторую комбинацию динамических и статических методов.Возможно, даже метапрограммирование.

Комбинации и вариации также были бы интересны.Например, я мог бы хотеть использовать только самый короткий входной файл.Или я мог бы хотеть запустить все источники с угловыми случаями для gain и level.Или gain также может быть непрерывным диапазоном от 1 до 100, но давайте пока что будем обособляться.

Прежде чем я начну проектировать типы, шаблоны, представления и т. Д., Я подумал, что эта проблема решенадо или, если нет, будут ли полезны какие-либо существующие библиотеки, например Boost MPL?

Ответы [ 3 ]

3 голосов
/ 24 марта 2011

Я думаю, что было бы полезно, если бы вы познакомились с концепцией Тестирование всех пар и провели бы быструю проверку для QuickCheck (это тестовая среда Haskell, которая генерирует тестслучайным образом в соответствии с заданной спецификацией, а затем проверяет, что некоторые свойства сохранены; существует версия C ++ * ).

Что касается, в частности, Boost.MPL, я не думаю, чтоможет помочь вам в решении этой задачи: вы не имеете дело со списком типов здесь, не так ли?

Мой еще один совет относительно вашего будущего дизайна: не перегружайте.Прежде чем начать с типами, шаблонами и т. Д., Реализуйте 3 (три) разумно разных реализации, а затем обобщите то, что у вас уже есть.

2 голосов
/ 25 марта 2011

Вас может заинтересовать Template2Code framework.Он специально разработан для решения вашей проблемы.Полная документация здесь .Согласно документации, вы должны создать *. T2c файл следующей структуры для генерации полного набора тестовых векторов:

<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>

Эта технология использовалась LinuxFoundation и ISPRAS для создания "нормальных" тестов качества для libstdcxx, glib, gtk, fontconfig, freetype и других библиотек.

1 голос
/ 24 марта 2011

Было очень заманчиво подумать об этой очень дружественной для программиста задаче:)

Здесь я пришел с динамическим решением, использующим boost :: any в качестве среды для хранения «стертых» типов. Более статичное решение, возможно, действительно использует Boost.Tuple и Boost.Fusion / Boost.MPL, но я не уверен, что оно того стоит.

Код имеет качество прототипа, и вы наверняка не собираетесь использовать его как есть. Но, по крайней мере, это может дать вам направление.

Итак, мини-рамки:

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;
};

Некоторые бетонные излучатели:

// 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); }

Адаптер для 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]));
    }
};

Наконец, использование:

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);

Реализация зверя "все пары" (для "минимального" парня) оставлена ​​вам для реализации%)

...