2010-02-26 23 views

回答

19

單元測試只需要「切割平面」或可以完成測試的邊界。測試不調用其他函數的C函數或只調用其他被測試函數的C函數也很簡單。這樣的一些例子是執行計算或邏輯操作的功能,並且本質上是功能性的。在相同的輸入總是導致相同的輸出的意義上的功能。測試這些函數可以有很大的好處,儘管它通常被認爲是單元測試的一小部分。

更復雜的測試(例如使用模擬或存根)也是可能的,但它不像使用更多動態語言或甚至只是面向對象的語言(如C++)那樣容易。解決這個問題的一種方法是使用#defines。其中一個例子是這篇文章,Unit testing OpenGL applications,它展示瞭如何模擬OpenGL調用。這使您可以測試是否有有效的OpenGL調用序列。

另一種選擇是利用弱符號。例如,所有MPI API函數都是弱符號,所以如果您在自己的應用程序中定義了相同的符號,那麼您的實現將覆蓋庫中的弱實現。如果庫中的符號不​​弱,則在鏈接時會出現重複的符號錯誤。然後,您可以實現實際上對整個MPI C API的模擬,這可以確保調用正確匹配,並且沒有任何可能導致死鎖的額外調用。也可以使用dlopen()dlsym()加載庫的弱符號,並在必要時傳遞該呼叫。 MPI實際上提供了強大的PMPI符號,所以沒有必要使用dlopen()和朋友。

您可以認識到C的單元測試的許多好處。它稍微難一點,並且可能無法獲得您用Ruby或Java編寫的東西所期望的相同級別的覆蓋率,但它絕對值得這樣做。

+3

迄今爲止的最佳答案。你甚至會展示如何處理C語言中對依賴注入的實際需求,這是隔離代碼覆蓋以實現真正的單元測試的主要技術。 – kirakun 2011-04-04 14:37:49

11

在最基本的層次上,單元測試只是執行其他代碼的代碼位,並告訴你它們是否按預期工作。

您可以使用main()函數簡單地創建一個新的控制檯應用程序,該應用程序執行一系列測試函數。每個測試都會在您的應用程序中調用一個函數,並返回0表示成功或另一個失敗值。

我想給你一些示例代碼,但我真的生鏽與C我肯定有一些框架,這將使這更容易一些。

+4

如果你還在尋找一個框架,試試這個列表中可用的C單元測試框架:HTTP ://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C – tobsen 2010-02-26 20:25:08

+0

啊哈優秀列表。比我想象的更多的框架。 – 2010-02-26 21:56:58

1

我使用assert。這不是一個真正的框架。

+4

斷言對測試並不好,因爲您的測試程序將在第一次失敗時終止,這嚴重限制了自動化測試和測試報告。 – 2010-02-26 17:36:47

+0

Assert可以用作單元測試的一部分或確保您的函數被正確調用的一部分,但它們不是「單元測試」 – 2010-02-26 17:48:37

3

執行單元測試最簡單的方法是構建一個簡單的驅動程序代碼,該代碼與其他代碼鏈接,並在每種情況下調用每個函數...並聲明函數結果的值並構建點點滴滴......這就是我如何做到這一點呢

 
int main(int argc, char **argv){ 

    // call some function 
    int x = foo(); 

    assert(x > 1); 

    // and so on.... 

} 

希望這有助於 最好的問候, 湯姆。

4

在隔離測試小塊代碼時,本質上沒有任何面向對象的東西。在程序語言中,您可以測試其功能和集合。

如果你很絕望,而且你必須絕望,我會在一起使用一個基於C預處理器和gmake的小框架。它起初是一種玩具,從來沒有真正長大過,但我用它來開發和測試幾個中型(10,000+線)項目。

Dave's Unit Test雖然侵擾性很小,但它可以做一些測試,我原本以爲基於預處理器的框架是不可能的(你可以要求某些代碼在某些條件下拋出分段錯誤,並且會測試它爲你)。

這也是爲什麼大量使用預處理器是爲了安全地做的一個例子。

1

對於C來說,它必須比現有代碼更簡單地實現一個框架。

我一直以來做的一件事就是製作一個測試模塊(帶一個main),你可以運行一些測試來測試你的代碼。這使您可以在代碼和測試周期之間做非常小的增量。

更大的問題是編寫代碼是可測試的。關注不依賴共享變量或狀態的小型獨立函數。嘗試以「功能」方式(無狀態)書寫,這將更容易測試。如果你的依賴關係不能總是存在或者很慢(比如數據庫),那麼你可能需要編寫一個完整的「模擬」層,在測試過程中可以替代你的數據庫。

原則單元測試目標仍然適用:確保被測代碼總是復位爲給定的狀態,不斷的測試,等等

當我在C(Windows之前回)寫代碼,我有一個批次這個文件會帶出一個編輯器,然後當我完成編輯和退出後,它會編譯,鏈接,執行測試,然後在不同的窗口中顯示編譯結果,測試結果和代碼。在我休息之後(根據正在編譯的內容,一分鐘到幾個小時),我可以只查看結果並直接回到編輯。我確信這個過程可以在這幾天得到改進:)

6

您可以使用libtap,它提供了一些功能,可以在測試失敗時提供診斷功能。其使用示例:

#include <mystuff.h> 
#include <tap.h> 

int main() { 
    plan(3); 
    ok(foo(), "foo returns 1"); 
    is(bar(), "bar", "bar returns the string bar"); 
    cmp_ok(baz(), ">", foo(), "baz returns a higher number than foo"); 
    done_testing; 
} 

它與其他語言的輕拍庫類似。

3

下面是一個示例,說明如何在可能調用庫函數的給定函數的單個測試程序中實現多個測試。

假設我們要測試以下模塊:

#include <stdlib.h> 

int my_div(int x, int y) 
{ 
    if (y==0) exit(2); 
    return x/y; 
} 

然後,我們創建了以下測試程序:

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <setjmp.h> 

// redefine assert to set a boolean flag 
#ifdef assert 
#undef assert 
#endif 
#define assert(x) (rslt = rslt && (x)) 

// the function to test 
int my_div(int x, int y); 

// main result return code used by redefined assert 
static int rslt; 

// variables controling stub functions 
static int expected_code; 
static int should_exit; 
static jmp_buf jump_env; 

// test suite main variables 
static int done; 
static int num_tests; 
static int tests_passed; 

// utility function 
void TestStart(char *name) 
{ 
    num_tests++; 
    rslt = 1; 
    printf("-- Testing %s ... ",name); 
} 

// utility function 
void TestEnd() 
{ 
    if (rslt) tests_passed++; 
    printf("%s\n", rslt ? "success" : "fail"); 
} 

// stub function 
void exit(int code) 
{ 
    if (!done) 
    { 
     assert(should_exit==1); 
     assert(expected_code==code); 
     longjmp(jump_env, 1); 
    } 
    else 
    { 
     _exit(code); 
    } 
} 

// test case 
void test_normal() 
{ 
    int jmp_rval; 
    int r; 

    TestStart("test_normal"); 
    should_exit = 0; 
    if (!(jmp_rval=setjmp(jump_env))) 
    { 
     r = my_div(12,3); 
    } 

    assert(jmp_rval==0); 
    assert(r==4); 
    TestEnd(); 
} 

// test case 
void test_div0() 
{ 
    int jmp_rval; 
    int r; 

    TestStart("test_div0"); 
    should_exit = 1; 
    expected_code = 2; 
    if (!(jmp_rval=setjmp(jump_env))) 
    { 
     r = my_div(2,0); 
    } 

    assert(jmp_rval==1); 
    TestEnd(); 
} 

int main() 
{ 
    num_tests = 0; 
    tests_passed = 0; 
    done = 0; 
    test_normal(); 
    test_div0(); 
    printf("Total tests passed: %d\n", tests_passed); 
    done = 1; 
    return !(tests_passed == num_tests); 
} 

通過重新定義assert更新布爾變量,你可以如果繼續斷言失敗並運行多個測試,記錄成功的次數和失敗的次數。

在每次測試開始時,將rslt(由assert宏使用的變量)設置爲1,並設置控制存根函數的所有變量。如果您的某個存根被多次調用,您可以設置控制變量數組,以便存根可以檢查不同調用中的不同條件。

由於許多庫函數都是弱符號,它們可以在測試程序中重新定義,以便它們被調用。在調用要測試的函數之前,可以設置許多狀態變量來控制存根函數的行爲,並檢查函數參數的條件。

如果您不能像這樣重新定義,則爲存根函數指定一個不同的名稱,並重新定義要測試的代碼中的符號。例如,如果要存根fopen但發現它不是弱符號,請將您的存根定義爲my_fopen並編譯文件以使用-Dfopen=my_fopen進行測試。

在這種特殊情況下,要測試的功能可能會調用exit。這很棘手,因爲exit無法返回到正在測試的功能。這是使用setjmplongjmp時有意義的罕見時間之一。在輸入要測試的函數之前,您使用setjmp,然後在exit中,您撥打longjmp直接返回到您的測試用例。

另請注意,重新定義的exit有一個特殊變量,它會檢查您是否確實想要退出程序並調用_exit來這樣做。如果你不這樣做,你的測試程序可能不會乾淨地退出。

該測試套件還計算嘗試和失敗測試的次數,如果所有測試都通過,則返回0,否則返回1。這樣,make可以檢查測試失敗並採取相應措施。

上面的測試代碼將輸出以下內容:

-- Testing test_normal ... success 
-- Testing test_div0 ... success 
Total tests passed: 2 

而返回代碼爲0。

相關問題