【问题标题】:Automatic / templated generation of test vectors in C++在 C++ 中自动/模板化生成测试向量
【发布时间】:2011-07-22 00:56:07
【问题描述】:

我想找到一种自动生成测试向量的好方法。例如,我正在通过调用一个函数来测试音频处理模块,该函数使用指定的测试向量来练习被测模块,并在此过程中对模块输出的正确操作和正确性进行各种检查。

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

测试向量是sourcegainlevel 的三元组。这是我要测试的多维空间:

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++,我希望使用模板以及动态和静态技术的某种组合。甚至可能是元编程。

组合和变化也会很有趣。例如,我可能只想使用最短的输入文件。或者我可能想运行带有gainlevel 的极端情况的所有源。或者gain 也可以是 1 到 100 的连续范围,但我们暂时保持离散。

在我开始设计类型、模板、表示等之前,我想知道这是否是一个以前已经解决的问题,或者如果没有,是否存在任何现有的库,例如Boost MPL,有用吗?

【问题讨论】:

  • 为什么需要模板?嵌套的 for 循环还不够吗?
  • 我不一定需要模板,但我只想写一次“allPermutations”、“cornerCases”、“minimal”、“allPairs”等,以应对任意数量的维度和所有参数类型。
  • 好吧,我错过了。最好的方法可能是使用基于例如的通用接口。 boost::any 用于传递测试参数。这样,您就可以将调度和参数分配与测试本身分开。在这方面,如果您不想使用特定框架,@Alexander Poluektov 的解决方案似乎足够灵活。

标签: c++ unit-testing templates metaprogramming


【解决方案1】:

我认为如果你介绍一下All-pairs testing 的概念,并快速检查一下QuickCheck,我认为这会很有用(它是 Haskell 测试框架,它根据给定的规范随机生成测试用例,然后检查一些属性被持有;存在C++ version of it)。

特别是关于 Boost.MPL,我认为它根本不会帮助您完成这项任务:您不是在这里处理类型列表,是吗?

我对您即将推出的设计的另一个建议是:不要过度概括。 在开始使用类型、模板等之前,先实现 3(三)个相当不同的实现,然后概括您手头已有的实现。

【讨论】:

  • 回复。类型列表——这正是我正在处理的,不是吗?每个参数可以是不同的类型,并且可以有任意数量。全对测试看起来像是添加到穷举、角落案例等的有趣武器。
  • 我宁愿在这里使用更动态的解决方案,而不是类型列表。很快就会发布一些代码。
【解决方案2】:

想一想这个对程序员非常友好的任务非常有诱惑力:)

在这里,我提出了使用 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);

实现所有对的野兽(对于“最小”家伙)留给您实现 %)

【讨论】:

  • 看起来不错,谢谢!稍后会尝试这个。之前没用过 boost::any。
  • 感谢您标记我的答案。但是知道我认为拥有“迭代器”概念而不是“发射器”会更合适:无论如何,您都需要在 Emitter 类中使用 reset() 方法,所以为什么不给概念和操作提供良好的惯用名称(迭代器、开始、结束、 ++)。无论如何,我已经调试了我发布的代码,所以如果您对修复感兴趣,请在这里写下一行。
  • 我同意迭代器更有意义,并且深入研究了这一点,也许甚至 Boost“范围”概念更好。这允许对输入数据集进行非常强大的范围操作,这意味着我只需要在多维空间上操作的少数“生成器”:穷举、全值、全对。
【解决方案3】:

您可能对Template2Code 框架感兴趣。它专为解决您的问题而设计。综合文档是here。根据文档,您应该创建一个具有以下结构的*.t2c file 以生成一组完整的测试向量:

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

The Linux FoundationISPRAS 使用这项技术为 libstdcxx、glib、gtk、fontconfig、freetype 和其他库创建了 "normal"-quality tests

【讨论】:

    猜你喜欢
    • 2010-11-25
    • 2019-10-19
    • 2016-09-13
    • 2022-11-16
    • 1970-01-01
    • 2021-04-21
    • 2011-02-18
    • 2013-08-02
    • 2017-12-26
    相关资源
    最近更新 更多