2011-03-24 34 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和其他圖書館。