我想找到一种自动生成测试向量的好方法。举例来说,我正在通过调用一个函数来测试音频处理模块,该函数使用指定的测试向量来运行被测模块,并且这样做会对模块输出的正确操作和正确性进行各种检查。
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的连续范围,但现在让我们保持离散。
在我开始设计类型,模板,表示等之前,我想知道这是否是之前已经解决过的问题,或者如果没有,是否会解决任何现有的库,例如:提升MPL,有用吗?
答案 0 :(得分:3)
我认为如果你自己介绍All-pairs testing的概念并快速检查QuickCheck会很有用(它是Haskell测试框架,根据给定的规范随机生成测试用例,然后检查是否存在某些属性;存在C++ version of it)。
特别是关于Boost.MPL,我认为它根本不会帮助你完成这项任务:你不是在这里处理类型列表,不是吗。
我对你即将推出的设计的另一个建议:不要过度概括。 在开始使用类型,模板等之前,实现3个(三个)相当不同的实现,然后概括您已有的内容。
答案 1 :(得分: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 Foundation和ISPRAS使用此技术为libstdcxx,glib,gtk,fontconfig,freetype和其他库创建"normal"-quality tests。
答案 2 :(得分:1)
想一想这个程序员友好的任务是非常诱人的:)
在这里,我带来了动态解决方案,使用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);
实施所有对兽(对于'最小'人)留给你实现%)