2009-02-27 159 views
33

作爲將單元測試一些C代碼,我們碰上了一些靜態函數不能在測試文件被稱爲問題,無需修改源代碼。有沒有簡單或合理的方法來解決這個問題?如何測試一個靜態函數

+0

可能重複[如何測試具有私有方法,字段或內部類的類?](https://stackoverflow.com/questions/34571/how-do-i-test-a-class-that -has-private-methods-fields-or-inner-classes) – Raedwald 2017-12-14 12:48:06

回答

42

我有一個測試用具。在嚴峻的情況下 - 就像試圖測試靜電功能,我使用:

#include "code_under_test.c" 
...test framework... 

也就是說,我包括整個包含在測試工具測試功能的文件。這是最後的手段 - 但它有效。

+0

我認爲這應該沒有任何影響。 – 2009-03-05 04:39:59

+0

我不明白這一點。有人可以請解釋。謝謝! – 2014-11-12 06:35:24

+1

@BhupeshPant:你不明白什麼?如果一個函數是`static`,它就不能在定義它的轉換單元之外(粗略地說,源文件)訪問。答案中的解決方案確保將要測試的函數包含在測試代碼中,方法是將要測試的源代碼複製到通過`#include「code_under_test.c」`指令進行測試的文件中。編譯器將要測試的代碼以及將測試全部作爲單個翻譯單元的代碼看到,因此測試代碼可以調用靜態函數,否則這是不可能的。 – 2014-11-12 06:38:20

2

你可以添加一個非靜態函數調用靜態函數,然後調用非靜態函數。

static int foo() 
{ 
    return 3; 
} 

#ifdef UNIT_TEST 
int test_foo() 
{ 
    if (foo() == 3) 
    return 0; 

    return 1; 
} 
#endif 
+0

修改源代碼,不是嗎? – 2009-02-27 03:38:46

+0

是的,它修改了源,但並沒有從foo中移除「靜態」。否則,答案几乎是你不能從外部調用靜態的。我自由了。 – 2009-02-27 03:44:12

5

沒有 - 你不能直接測試的靜態函數無需修改源至少有一點(即在C中的靜態定義 - 它不能從一個函數在不同的文件調用)。

您可以創建測試文件,僅僅調用靜態函數中的一個單獨的功能?

例如:

//Your fn to test 
static int foo(int bar) 
{ 
    int retVal; 
    //do something 
    return retVal; 
} 

//Wrapper fn 
int test_foo(int bar) 
{ 
    return foo(bar); 
} 

我們通常不直接考驗我們的靜態功能,而是確保他們執行的邏輯是充分的被調用函數的不同的測試進行測試。

11

你可以提供更多的信息,爲什麼你不能調用該函數?

它不可用,因爲它對於.c文件是私人的嗎?如果是這樣,最好的辦法是使用條件編譯來訪問函數,以便其他編譯單元可以訪問它。例如

SomeHeaderSomewher.h

#if UNIT_TEST 
#define unit_static 
#else 
#define unit_static static 
#endif 

foo.h中

#if UNIT_TEST 
void some_method 
#endif 

Foo.cpp中

unit_static void some_method() ... 
6

用於單元測試,我們實際上有在源文件中的測試碼本身,我們在測試時有條件地編譯它。這使單元測試可以完全訪問所有函數和文件級變量(靜態或其他)。

單元測試本身不是一成不變的 - 這使我們能夠從單元測試所有編譯單元一個超級測試程序調用的單元測試。

當我們的船的代碼,我們有條件地編出了單元測試,但是這實際上不是必要的(如果你想確定你是運送正是你測試相同的代碼)。

我們一直認爲它無價有單元測試在同一個地方,你正在測試,因爲它使得它更明顯,你需要更新測試是否以及何時改動代碼的代碼。

2

靜態函數實質上是公共(即暴露)函數的輔助函數。所以IMO,你的單元測試應該使用靜態函數中所有路徑的輸入調用公共接口。

公共函數的輸出(返回值/副作用)應該用於測試靜態效果。

這意味着你需要有適當的存根來捕捉這些副作用。 (例如,如果函數調用文件IO,則需要提供存根以覆蓋這些文件IO lib函數)。通過使每個測試套件都是獨立的項目/可執行文件並避免鏈接到任何外部lib函數來實現此目的的最佳方法。你甚至可以嘲笑C函數,但這不值得。

無論如何,這是我迄今使用的方法,它適用於我。祝你好運

2

如果你是Unix環境下您可以在測試文件附加頭yourheader_static.h與您的靜態函數的聲明和翻譯OBJ文件code_under_test.o通過objdump --globalize-symbols=syms_name_file全球化局部符號。它們將可見,就好像它們是非靜態函數一樣。

0

有2種方法可以做到這一點。

  1. 包括C源文件到單元測試源文件,所以靜態方法現在在單元測試源文件和可調用的範圍。

  2. 做一招:

#define static

在單元測試源文件的頭部

它會將關鍵字static轉換爲「無」,或者我可以說,它會從您的c源代碼中刪除static

在一些單元測試工具,我們可以使用配置選項「預處理器」做這一招,只是把在配置:

static=

刀具預訂購轉換所有static關鍵字「沒什麼「

但我必須說,使用這些方法使得static方法(或變量)失去了它的特定含義。

1
#define static 

這是一個非常糟糕的主意。如果你有一個聲明爲函數本地的變量,它會改變函數的行爲。例如:

static int func(int data) 
{ 
    static int count = 0; 

    count += data; 
    return count; 
} 

你可以從單元測試中調用函數,因爲func()會被導出,但是代碼的基本功能會被修改。

--kurt

1

如果您使用Ceedling並試圖使用的#include「code_under_test.c」的方法,該測試版本將失敗,因爲它會自動嘗試建立「code_under_test.c」當一次#included,也因爲它是測試的目標。

我已經能夠通過稍微修改code_under_test.c代碼和其他一些更改來解決它。與此檢查整個包住code_under_test.c文件:

#if defined(BUILD) 
... 
#endif // defined(BUILD) 

添加到您的測試工具:

#define BUILD 
#include "code_under_test.c" 

添加BUILD定義到你的Makefile或項目的配置文件:

# Makefile example 
.. 
CFLAGS += -DBUILD 
.. 

現在,您的文件將從您的環境中構建,並從測試工具中包含。 Ceedling現在不能再次構建文件(確保您的project.yml文件不定義BUILD)。

1

以上所有建議的答案(除少數)都建議進行條件編譯,這需要修改源文件。因此,這不應該是一個問題,它只會增加混亂(僅用於測試)。相反,你可以做這樣的事情。

說你的功能進行測試的

static int foo(int); 

你讓叫testing_headers.h另一頭文件,這將有內容 -

static in foo(int); 
int foo_wrapper(int a) { 
    return foo(a); 
} 

現在在編譯的C文件進行測試即可從編譯器選項強制包含此頭文件。

對於clang和gcc,標誌是--include。對於Microsoft C編譯器,它是/FI

這將需要對您的c文件進行絕對0的更改,您將能夠爲您的函數編寫一個非靜態包裝。

如果你不想要一個非靜態的包裝器,你也可以創建一個非靜態的全局函數指針初始化爲foo。

然後,您可以使用此全局函數指針調用該函數。

0

只需添加到喬納森·萊弗勒接受的答案,並詳細說明其他的提到的包裝功能:

  1. 創建一個測試源文件,如test_wrapper_foo.c,其中foo.c的是原。
  2. 在test_wrapper_foo.c:
#include "foo.c" 

// Prototype 
int test_wrapper_foo(); 

// wrapper function 
int test_wrapper_foo() { 
    // static function to test 
    return foo(); 
} 

假設foo.c中靜態函數foo原型:int foo的(無效);

透過生成文件的文件foo.c代替

  • 構建test_wrapper_foo.c(注意,這將不會被其它外部函數打破功能有關的任何依賴性foo.c中)

  • 在您的單元測試腳本中,調用test_wrapper_foo()而不是foo()。

  • 這種方法使原始源保持完好,同時允許您從測試框架訪問函數。