2011-03-24 36 views
5

我想找到一种自动生成测试向量的好方法。举个例子,我正在通过调用一个函数来测试一个音频处理模块,该函数使用指定的测试向量来执行被测模块,并且这样做会对模块输出的正确操作和正确性进行各种检查。在C++中自动/模板生成测试向量

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,有用吗?

+0

为什么你想要模板?不足以嵌套循环吗? – 2011-03-24 13:25:36

+0

我不一定需要模板,但我只想编写“allPermutations”,“cornerCases”,“minimal”,“allPairs”等一次来处理任意数量的维度和所有参数类型。 – paperjam 2011-03-24 14:18:39

+0

好吧,我错过了这个。最好的方法可能是使用基于例如。 'boost :: any'用于传递测试参数。这样,您可以将调度和参数分配与测试本身分开。在这方面,@Alexander Poluektov的解决方案似乎足够灵活,如果你不想使用特定的框架。 – 2011-03-24 14:26:28

回答

1

这是非常诱人的想一下我使用boost ::任何为介质来存储“擦除”类型与动态的解决方案来到这个非常程序员友好的任务:)

这里。 更静解决方案可能会使用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); 

实现全对兽(对于“最小”的家伙)留给你实现%)

+0

看起来不错,谢谢!稍后再试。之前没有使用boost :: any。 – paperjam 2011-03-24 15:27:56

+0

感谢您标记我的答案。但是我知道我认为拥有“迭代器”概念而不是“发射器”会更合适:无论如何,您需要在Emitter类中使用reset()方法,为什么不直接给概念和操作提供良好的惯用名称(迭代器,开始,结束,++)。无论如何,我已经调试了我发布的代码,所以如果您对修复感兴趣,请在此处放置一行代码。 – 2011-03-28 17:50:45

+0

我同意迭代器更有意义,并深入了解了这一点,或许即使是Boost“范围”概念也更好。这使得输入数据集上的范围操作非常强大,这意味着我只需要少数几个在多维空间上操作的“生成器”:穷举,全值,全对。 – paperjam 2011-03-30 08:54:34

3

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

关于Boost.MPL,我不认为它完全有助于你完成这项任务:你不是在处理类型列表,你是。

我的另一个建议你的即将到来的设计:不要过度概括。 在开始使用类型之前,模板等实现了3个(三个)合理不同的实现,然后推广您已有的东西。

+0

Re。类型列表 - 这正是我正在处理的,不是吗?每个参数可以是不同的类型,并且可以有任何数量的参数。全对测试看起来像是一个有趣的武器,可以添加到穷举,角落案例等。 – paperjam 2011-03-24 12:43:22

+0

我宁愿在这里使用比列表类型更动态的解决方案。会尽快发布一些代码。 – 2011-03-24 12:46:39

+0

+1对所有对。 – mskfisher 2011-08-02 14:24:21

2

您可能会感兴趣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创造"normal"-quality tests为libstdcxx,巧舌如簧,GTK,fontconfig的是,FreeType和其他图书馆。