2011-02-03 98 views
2

我正在尋找一種方法來找出位於同一個C文件中的幫助程序方法。有沒有辦法做到這一點,而無需修改源文件?我想一起使用#defineb_stub方法替換方法b線的東西,但我認爲這最終會重命名方法b單元測試存根C輔助函數方法

這裏有一個樣本用例:

#include "file.h" 

a(){ 
    b(); 
} 

b(){ 
} 

我試圖創建一個測試框架,但我希望用戶只需包含一個包含框架和存根定義的文件。

謝謝。

+0

我很好奇。你爲什麼不能修改源代碼? – 2011-02-03 02:17:15

回答

3

我不知道我完全理解你的問題。

如果你想調用不同的程序比b,那麼你可以在編譯時間做到這一點:

a() { 
#ifdef USE_STUB 
    b_stub(); 
#else 
    b(); 
#endif 
} 

或者,如果你總是想打電話b,但要b不同的行爲,你可以做到這一點在編譯時間:

a() { 
    b(): 
} 
b() { 
#ifdef USE_STUB 
    printf("I am in the stub version of b()\n"); 
#else 
    printf("I am in the real version of b()\n"); 
#endif 
} 

或者你可以在運行時用(這裏顯示與簡單的全局變量)做類似的事情:

a() { 
    extern int _use_stub; 
    if(_use_stub) { 
     b_stub(); 
    } else { 
     b(); 
    } 
} 

a() { 
    b(); 
} 
b() { 
    extern int _use_stub; 
    if(_use_stub) { 
     printf("This is the stub code\n"); 
    } else { 
     printf("This is the real code\n"); 
    } 
} 

隨着編譯時的例子可以通過改變標頭文件或生成文件定義來回切換。通過運行時示例,您可以使用命令行選項,環境變量,用戶首選項窗格或其他任何方法來回切換。

0

如果這是在框架代碼,而不是最終用戶代碼,那麼你可以做這樣的事情

#ifndef UNIT_TEST_FUNC_B 
b() 
{ 
} 
#endif 

,現在當你想運行B上的單元測試,你定義UNIT_TEST_FUNC_B,包括存根代碼在一個單獨的模塊

,或者如果你想保持測試代碼在同一模塊中,你這樣做

#ifndef UNIT_TEST_FUNC_B 
b() 
{ 
} 
#else 
b() 
{ 
// test code goes here 
} 
#endif 

我使用的唯一名稱定義,所以你可以爲不同的測試劃出不同的功能。

0

您必須修改源,但不是太多。

試試這個

#define b() stub_b() 

a(){ 
    b(); 
    } 

(b)() 
{ 

} 

現在調用方法B()將stub_b()和b()認定中被替換將保持不變。 :)

1

我找到了一個解決方案,這對我很有用,也許它也會幫助你。

自己使用MACROS只能讓你到目前爲止。如果你想對一個函數執行一個測試,但是使用MACROS以各種不同的方式將其存根出來,將需要你重新編譯你的代碼幾次並且單獨運行每個條件。這很難自動化 - 現在你必須有一個批處理腳本來定義不同的符號並重建代碼並彙總結果。

但是,如果您使用MACROS爲每個需要存檔的函數定義函數指針,則假設您可以對希望測試的目標代碼進行一些小修改,則可以使用一種可行的解決方案。

以下示例在很大程度上受到以下因素的影響:

  1. http://eradman.com/posts/tdd-in-c.html
  2. http://locklessinc.com/articles/mocking/
  3. http://www.embedded.com/design/programming-languages-and-tools/4007177/2/Doing-C-code-unit-testing-on-a-shoestring-Part-1-The-basics-and-the-tools

MUT在本例中代表模塊下測試。假設你有四個文件:mut.h,mut.c,test_mut.h,test_mut.c。讓我們也假設你可以在構建它時定義一個符號UNIT_TEST。

mut.h將包含任何可公開訪問的函數。對於這個例子,沒有任何,所以讓我們忘記它。

所以讓我們開始與一個版本mut.c

#include <cstdbool> 
#include "mut.h" 

static bool foo(int baz); 
static bool bar(int baz); 

static bool foo(int baz) 
{ 
    bool ret = false; 

    if(bar(baz)) 
    { 
     //do something 
     ret = true; 
    } 
    else 
    { 
     ret = false; 
    } 
    return ret; 
} 

static bool bar(int baz) 
{ 
    //Black box mystery/Don't care 
} 

的假設,我們已經進行單元測試吧。它工作正常。現在我們想測試foo,但是我們不想設置所需的所有東西,以便使條形圖正確執行。所以我們需要存根欄。

因此,讓我們包含一個新的頭文件test_mut.h。除此之外,你將不得不在test_mut.h

#ifdef UNIT_TEST 
... 

//Convert every static definition into an extern declaration. 
#define static extern 

bool bar_mock_true (int baz); 
bool bar_mock_false(int baz); 
bool bar_real  (int baz); 

extern bool(*bar_ptr)(int baz); 
#define bar bar_ptr 

... 
#endif 

所以下面,你可以看到我們定義一個新的函數指針現在可以指向我們的存根/嘲笑或到我們真正的酒吧功能。這個頭文件也會包含在test_mut.c中,所以現在可以在test_mut.c文件中定義存根函數 - 它們不需要在mut.c中混淆。

現在讓我們使這是有用的,我們需要修改mut.c小幅

需要單元測試過程中被禁用

mut.c現在需要包括「test_mut.h」,酒吧的標準聲明() ,我們需要改變函數的定義bar_real()

... 
#include "test_mut.h" 
... 
#ifdef UNIT_TEST 
static bool bar(int baz); 
#endif 

... 

#ifdef UNIT_TEST 
static bool bar_real(int baz) 
#else 
static bool bar(int baz) 
#endif 
{ 
    //Black box mystery/Don't care 
} 

需要存根出需要類似的#ifdefs和周圍的聲明和定義重命名每個功能。所以你的代碼在測試中會不幸地需要一點點混亂。

現在test_mut.c現在可以鍛鍊你的代碼如下:

#include <cstdbool> 
#include "test_mut.h" 

... 

UT_STATIC void test_foo(void) 
{ 
    int baz = 0; 
    extern bool foo(int baz); 
    //Test Case A 
    bar_ptr = bar_mock_true; 
    TEST_ASSERT(foo(baz), "Condition A"); 

    //Test Case B 
    bar_ptr = bar_mock_false; 
    TEST_ASSERT(!foo(baz), "Condition B"); 
} 

bool bar_mock_true(int baz) 
{ 
    return true; 
} 

bool bar_mock_false(int baz) 
{ 
    return false; 
} 

這裏是我的源文件的一些全列表。我已經在這裏建立了一個輕量級的測試工具,它編譯並在IAR嵌入式平臺編譯運行(我還沒有嘗試過的其他任何東西),併產生以下輸出

.. 運行測試:2

mut.c

#include <cstdbool> 

#include "mut.h" 
#include "test_mut.h" 

static bool foo(int baz); 

#ifndef UNIT_TEST 
static bool bar(int baz); 
#endif 

static bool foo(int baz) 
{ 
    bool ret = false; 

    if(bar(baz)) 
    { 
     //do something 
     ret = true; 
    } 
    else 
    { 
     ret = false; 
    } 
    return ret; 
} 

#ifdef UNIT_TEST 
static bool bar_real(int baz) 
#else 
static bool bar(int baz) 
#endif 
{ 
    //Black box mystery/Don't care 
} 

test_mut.h

#ifdef UNIT_TEST 
#ifndef _TEST_MUT_H 
#define _TEST_MUT_H 

//Handle to your test runner 
void test_mut(void); 

//Track the number of test cases that have executed 
extern int tests_run; 

//Convert every static definitions into extern declarations. 
#define static extern 

//An alternate definition of static for the test barness to use 
#define UT_STATIC static 

bool bar_mock_true (int baz); 
bool bar_mock_false (int baz); 
bool bar_real  (int baz); 

extern bool(*bar_ptr)(int baz); 

#define bar bar_ptr 

//Test Macros 
#define TEST_FAIL(name)               \ 
do                    \ 
{                     \ 
    printf("\nTest \"%s\" failed in %s() line %d\n", (name), __func__, __LINE__); \ 
} while(0) 

#define TEST_ASSERT(test_true,test_name)           \ 
do                    \ 
{                     \ 
    tests_run++;                 \ 
    if(!(test_true))                \ 
    {                    \ 
     TEST_FAIL(test_name);              \ 
    }                    \ 
    else                   \ 
    {                    \ 
     printf(".");                \ 
    }                    \ 
} while(0) 

//... Insert any other macro instrumentation you may need... 

#endif // _TEST_MUT_H 
#endif // UNIT_TEST 

test_mut.c

#ifdef UNIT_TEST 

#include <cstdbool> 
#include <cstdio> 
#include "test_mut.h" 
#include "mut.h" 

UT_STATIC void test_foo(void); 

int tests_run = 0; 

inline UT_STATIC void test_report(void); 

void test_mut(void) { 
    //call your setup function(s) 
    test_foo(); 
    //call your teardown function(s) 

    test_report(); 
} 

inline UT_STATIC void test_report(void) 
{ 
    printf("\nTests Run: %d\n", tests_run); 
} 

void main(void) 
{ 
    test_mut(); 
} 

//Setup the function pointer for bar, by default it will point to the real 
//bar function, and not a stub. 
bool(*bar_ptr)(int baz) = bar_real; 

UT_STATIC void test_foo(void) 
{ 
    int baz = 0; 
    extern bool foo(int baz); 

    //Test Case A 
    bar_ptr = bar_mock_true; 
    TEST_ASSERT(foo(baz), "Condition A"); 

    //Test Case B 
    bar_ptr = bar_mock_false; 
    TEST_ASSERT(!foo(baz), "Condition B"); 
} 

bool bar_mock_true(int baz) 
{ 
    return true; 
} 

bool bar_mock_false(int baz) 
{ 
    return false; 
} 

#endif