2014-02-13 66 views
6

注:我知道active_在我的例子中可能是「任何東西」。這不是這個問題的關鍵。這是關於使一個「未定義的值」可靠地通過單元測試。TDD:給定測試成員初始化,給定C++中未定義的行爲

編輯:從「no constructor」改爲「empty constructor」。

我正在研究C++類,並使用TDD。現在我想確保一個bool類成員被正確初始化 - 一個值在構造函數中賦值給它。所以我寫了下面的測試(使用谷歌模擬/谷歌測試框架):

TEST(MyClass, isNotActiveUponCreation) { 
    MyClass my; 
    ASSERT_FALSE(my.isActive()); 
} 

和下面的類定義:

class MyClass { 
public: 
    // Note: Constructor doesn't initialize active_ 
    MyClass() {} 

    bool isActive() const { return active_; } 
private: 
    bool active_; 
}; 

問題:在我的機器,這種測試目前總即使active_從未初始化。現在我們知道active_的值是未定義的,因爲它是一個原始類型並且從未初始化。所以從理論上講,在某些時候可能是true,但最後不可能知道。底線是,我不能可靠地測試使用這種方法缺少初始化。

有沒有人有一個想法,我怎麼可能以確定性和可重複性的方式來測試這種情況?或者我必須忍受它,省略這種測試,並希望我永遠不會忘記初始化一個布爾成員,或者其他測試總會產生缺陷?

+0

在構造函數中初始化該變量,否則可將其設置爲任何值。 –

+2

@RetiredNinja是的,我知道。但我想要的是確保構造函數設置它 - 使用TDD。所以我正在尋找一種方式讓我的測試可靠地失敗,以防'active_'被設置爲「任何東西」。我添加了一個註釋,以使其更清晰。 –

+1

只是一個想法,不是一個真正的答案:如果創建一個包含100個MyClass對象的std :: vector,該怎麼辦?那麼每個對象的'active_'都是false? – TobiMcNamobi

回答

3

閱讀TobiMcNamobi的回答後,我想起了placement new,並得到了一個想法如何解決我的問題。下面的測試失敗,可靠,除非我在構造函數初始化active_

#include <gmock/gmock.h> 
#include <vector> 

class MyClass { 
public: 
    // Note: Constructor doesn't initialize active_ 
    MyClass() {} 

    bool isActive() const { return active_; } 
private: 
    bool active_; 
}; 

TEST(MyClass, isNotActiveUponCreation) { 
    // Memory with well-known content 
    std::vector<char> preFilledMemory(sizeof(MyClass), 1); 

    // Create a MyClass object in that memory area using placement new 
    auto* myObject = new(preFilledMemory.data()) MyClass(); 


    ASSERT_FALSE(myObject->isActive()); 
    myObject->~MyClass(); 
} 

現在,我得承認,這個測試是不是最可讀的一個,並有可能不是一見鍾情立即清除,但它工作可靠和獨立的任何第三方工具,如valgrind。這值得額外的努力嗎?我不確定。它很大程度上取決於MyClass內部,這會使其變得非常脆弱。無論如何,這是一種方法來測試在C++中正確初始化的對象..

+1

有趣的,但你是依靠初始化記憶到一個錯誤的值,我認爲這在一般情況下是不切實際的。無論如何,有趣的問題。比我想象的要多得多,我必須承認! – user2672165

+1

+1。也許初始化爲'(char)1'仍然有點不安全。也許它應該是'(unsigned char)-1',以便每一位都是1.但是這是細節。反正很好的答案! – TobiMcNamobi

1

這樣的問題實際上很容易進行單元測試,一旦你有單元測試。

只要運行內存檢查器(valgrind在Linux上,不知道什麼是在Windows上使用)下的單元測試。

而不是創造GTEST可執行的,我創建了一個簡單的例子:

#include <iostream> 

class MyClass { 
public: 
    // Note: no constructor 

    bool isActive() const { return active_; } 
private: 
    bool active_; 
}; 

int main() 
{ 
    MyClass c; // line 17 

    std::cout << c.isActive() << std::endl; 
} 

下Valgrind的運行它,我得到了一個輸出(修剪不需要線):

==9217== 
==9217== Conditional jump or move depends on uninitialised value(s) 
..... 
==9217== by 0x40094F: main (garbage.cpp:17) 

當您執行單元用valgrind進行測試,你會得到所有與內存訪問有關的問題。你也會得到回溯。

+3

我希望看到一個純TDD的答案,並且不依賴於像valgrind這樣的工具。 – TobiMcNamobi

+0

這聽起來很有希望,儘管它增加了一些複雜性。你知道一個簡單的方法來自動化嗎?理想情況下,我希望能夠從我的gtest測試套件中運行這樣的檢查。 –

+0

@TobiMcNamobi我也想這樣的工具,但它不存在。也許有些靜態代碼檢查器,但那些不是TDD –

1

我的小測試:

#include <stdlib.h> 
#include <vector> 
#include <iostream> 

class MyClass { 
public: 
    // Note: Constructor doesn't initialize active_ 
    MyClass() {} 

    bool isActive() const { return active_; } 
private: 
    bool active_; 
}; 

int main(int argc, char* argv[]) 
{ 
    std::vector<MyClass> vec(1000); 
    for (int i = 0; i < 1000; i++) 
    { 
     std::cout << (vec[i].isActive() ? "1" : "0"); 
    } 

    return system("pause"); 
} 

時就執行(與VS2012編譯)會發生什麼?

調試配置:寫上千個1。

釋放配置:寫入了一千個0。我把迭代次數提高到了10萬,並得到了10萬個0 ......不等第幾個數字是10101110000000 ......那裏!在這樣的另一個序列之間的某個地方是隱藏的。

這是什麼意思?結果或多或少有所預期。你無法預測如何設置一個未初始化的位。你想在這裏做的是初始化內存中的一些空間,並在那裏創建一個對象,就好像該內存以前沒有初始化過一樣。

因此,直到我被證明是錯誤的:你不能單元測試它

除非您使用工具(例如valgrind,請參閱其他答案)。

我認爲TDD很棒,但它當然有其侷限性。我只是初始化標誌並繼續紅綠重構循環。

+0

「你想......初始化內存中的一些空間並在那裏創建一個對象......」 - 這應該不是新的放置位置可能嗎? –

+0

@致命吉他問題是,如果你調用'new',你不知道首先分配內存的地址。考慮下面的句子:「在某處創建Myclass *的新對象*並初始化所有未初始化的東西。」當然你可以說「給我的對象x(Myclass的類型或任何)的地址,現在爲對象y分配新的內存」,但是如何控制y被分配在x的位置?我不知道。但如果我知道,我會編輯或刪除這個答案。 – TobiMcNamobi

+0

C++有一個名爲[placement new]的語言功能(http://www.parashift.com/c++-faq/placement-new.html)。它允許你將一個對象「放置」到你分配的內存中 - 這樣你就可以用全1來填充一個緩衝區,把你的對象放在那裏然後測試。如果它能正常工作,我會給它一個答案並寫出我自己的答案。 –

1

TDD測試應該鍛鍊行爲而不是實施細節。構造函數初始化,setter初始化等的測試依賴於特定的實現,並且如果實現被重構,將會是脆弱的。

+0

好吧,但測試「isNotActiveUponCreation」只測試外部可見行爲作爲類接口的一部分,對嗎?問題在於,「未初始化」在C++中意味着「任意值」,而在其他語言(如Java,Python)中則不是這樣,在這種情況下,如果你不確定你的值是否爲null初始化它們 –

+0

儘管我自己的答案中對測試絕對是正確的,但只要MyClass不再使用「bool」成員來確定它是否「活動」,它就會中斷。 –

+1

在TDD中,測試首先被寫入並描述一些期望域行爲的小項目。 'isNotActiveUponCreation'沒有描述域的行爲,它描述了實現的細節:我們將有一個類,它將有一個'主動'屬性等等。決定讓一個具有'主動'屬性的類是通過創建一個某些項目的行爲失敗。我會看靜態代碼分析工具來檢測未初始化的成員,而不是單元測試。 –

0

你要做的是對未定義的行爲進行單元測試,這是毫無意義的,因爲所有的結果顯然都需要被接受。

+0

你可能會考慮閱讀我的整個問題;) –

0

如果你編譯你的單元測試套件MemorySanitizer,那麼從未初始化的內存讀取應該會導致測試失敗。

相關問題