2011-02-05 51 views
2

我在測試方面很新,所以請讓我知道,如果我在任何時候完全錯誤的方向進行。話雖如此,假設我想測試下面的函數foo。如何測試輸出依賴於另一個函數的函數?

int foo(int i) { 
    //Lots of code here 

    i = bar(); 

    //Some more changes to i happen here, conditional on what bar returned 

    return i; 
} 

在這個例子中,foo和bar都是自己編寫的函數,我已經測試過了。

由於foo的輸出是以bar的輸出爲條件的,我假設爲了測試foo,我需要創建一個模擬條。爲了做到這一點,並假設bar的定義保存在與foo不同的源文件中,我可以創建一個新的源文件,包含該文件,而不是發現bar的實際定義的源文件,並將模擬在該文件中的酒吧。

int bar(void) { 
    return HARD_CODED_VALUE; 
} 

然而,存在2個問題與該方法:

1),會發生什麼,如果杆返回多個值(例如作爲錯誤代碼或實際值)和I需要確保FOO正確地反應對於每種可能性?我無法爲欄創建多個定義。我曾經想過的一個想法是在bar中創建一個靜態int,然後每次調用bar時都會增加它。然後我只是有一個條件這個int,多次調用欄,從而返回多個值。不過,我不確定是否引入更復雜的邏輯成模擬功能是很好的做法,或者有更好的方式來實現這一點:如果酒吧是相同的源文件中foo

int bar(void) { 
    static int i = 0; 

    i++; 
    if(i == 1) { 
    return HARD_CODED_VALUE_1 
    } 
    else if(i == 2) { 
    return HARD_CODED_VALUE_2 
    } 
    else { 
    fprintf(stderr, "You called bar too many times\n"); 
    exit(1); 
    } 
} 

2)會發生什麼?我不能重新定義酒吧,也不能在不改變我的源代碼的情況下分開foo和bar,這將是一個真正的痛苦。

+1

foo函數可以在沒有模擬條功能的情況下進行單元測試嗎?如果是這樣,只需創建一個涵蓋foo和bar的組合功能的單元測試。 – 2011-02-05 12:46:50

回答

3

那麼,圍繞這個問題有幾種解決方法。

  • 你可以使用預處理掛鉤換出bar()UNITTEST標誌設置:

    #ifdef UNITTEST 
    return mockBar(); 
    #else 
    return bar(); 
    #endif 
    
  • 你可以模擬依賴注入,並且需要一個指向bar()作爲參數傳遞給函數。我並不是說這在實踐中是一個好主意,但你可以做到。

    void foo(void (*bar)()) { 
    

我敢肯定還有其他的,但是這只是2是來到了我的頭頂......

+0

預處理器掛鉤對我來說是最好的方式,我想。 – 2011-03-31 22:09:35

0

有用於嘲諷庫。這些庫通常找到解決這些問題的方法。先進的庫將允許您在測試中配置什麼bar()應該在測試的每個點返回。

我不確定他們是否會處理bar()foo()位於同一個源文件中的情況,但他們可能會這樣做。在這種情況下,我認爲bar()foo()是同一單元的一部分,但這是一個完全不同的論點。

這裏是一個來自GoogleMock的C++代碼片段(source)。它創建一個模擬烏龜對象,畫家應該調用一次PenDown方法,當它執行PenDown方法時將返回500。如果畫家不叫PenDown,那麼測試就會失敗。

#include "path/to/mock-turtle.h" 
#include "gmock/gmock.h" 
#include "gtest/gtest.h" 
using ::testing::AtLeast;      // #1 

TEST(PainterTest, CanDrawSomething) { 
    MockTurtle turtle;       // #2 
    EXPECT_CALL(turtle, PenDown())    // #3 
     .WillOnce(Return(500)); 

    Painter painter(&turtle);     // #4 

    EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); 
}            // #5 

int main(int argc, char** argv) { 
    // The following line must be executed to initialize Google Mock 
    // (and Google Test) before running the tests. 
    ::testing::InitGoogleMock(&argc, argv); 
    return RUN_ALL_TESTS(); 
} 

當然這個特定的圖書館正在使用你可能會或可能不會做的OOP。我猜想還有其他庫也用於非OOP。

+0

Heya Pace,感謝您的回覆,但我只是想強調一下,我認爲C和C++的測試是非常不同的,我完全使用C,根據我的問題標籤。 – 2011-02-05 12:55:42

1

你想要做的是用一個存根返回已知值來替代被調用的函數。當使用外部依賴關係(即數據庫或網絡代碼)時也是如此。用C有兩個可使用的「接縫」,允許你執行替代(使用的術語從修改代碼的工作):

  • 使用預處理器命令與宏,例如更換功能體

    #ifdef TEST 
    #define bar(x) { if (x) then y; else z; } 
    #endif 
    
  • 將bar(x)移動到單獨的庫中,然後維護該庫的兩個版本。第一個是你的生產代碼,第二個是包含bar(x)的測試存根的測試庫。

第三種選擇是使用依賴注入,通過重構所述杆(x)的呼叫到一個函數指針參數作爲ircmaxell證明。

void foo(void (*bar)()) 

我已經嘗試了這些方法與非OO C++代碼,並發現第一個是迄今爲止最有用的。第二個引入了一個非常艱難的可維護性問題(相同庫的多個版本和需要維護的功能),而後者顯然對代碼的可讀性和可理解性有負面影響。

的預處理指令,在另一方面,可以非常局部化和備用定義可以被分離成如果測試了僅包含一個頭文件,即

#ifdef TEST 
    #include "subsystem_unittest.h" 
#endif 
0

是bar()的尷尬依賴性?使用bar的實際實現對foo進行單元測試有問題嗎?

如果沒有,那麼我沒有看到問題。你不必嘲笑一切。

相關問題