2010-06-05 20 views
15

Tôi đang cố triển khai thử nghiệm đơn vị cho mã của mình và tôi đang gặp khó khăn khi thực hiện.C++ - Có thể thực hiện kiểm tra rò rỉ bộ nhớ trong thử nghiệm đơn vị không?

Lý tưởng nhất là tôi muốn kiểm tra một số lớp học không chỉ cho chức năng tốt mà còn để phân bổ bộ nhớ thích hợp/deallocation. Tôi tự hỏi nếu kiểm tra này có thể được thực hiện bằng cách sử dụng một khung kiểm tra đơn vị. Tôi đang sử dụng Visual Assert btw. Tôi rất thích xem một số mã mẫu, nếu có thể!

Trả lời

13

Bạn có thể sử dụng chức năng gỡ lỗi ngay vào phòng thu dev để thực hiện kiểm tra rò rỉ - miễn là thử nghiệm đơn vị của bạn 'chạy bằng cách sử dụng thời gian chạy c debug.

Một ví dụ đơn giản sẽ giống như thế này:

#include <crtdbg.h> 
struct CrtCheckMemory 
{ 
    _CrtMemState state1; 
    _CrtMemState state2; 
    _CrtMemState state3; 
    CrtCheckMemory() 
    { 
    _CrtMemCheckpoint(&state1); 
    } 
    ~CrtCheckMemory() 
    { 
    _CrtMemCheckpoint(&state2); 
    // using google test you can just do this. 
    EXPECT_EQ(0,_CrtMemDifference(&state3, &state1, &state2)); 
    // else just do this to dump the leaked blocks to stdout. 
    if(_CrtMemDifference(&state3, &state1, &state2)) 
     _CrtMemDumpStatistics(&state3); 
    } 
}; 

Và để sử dụng nó trong một thử nghiệm đơn vị:

khuôn khổ kiểm tra
UNIT_TEST(blah) 
{ 
    CrtCheckMemory check; 

    // TODO: add the unit test here 

} 

Một số đơn vị thực hiện phân bổ của riêng mình - Google ví dụ phân bổ khối khi một kiểm tra đơn vị không thành công, vì vậy bất kỳ khối thử nghiệm nào có lỗi vì bất kỳ lý do nào khác luôn luôn có một "rò rỉ" dương tính giả.

+2

Đây là một giải pháp rất "sạch". Tôi đã thử nó (trong một trường hợp thừa nhận đơn giản) và nó hoạt động như mong đợi. +1 – sevaxx

+1

Tôi sẽ làm điều đó. Đây là một giải pháp thực sự sạch sẽ.Chỉ cần gọi hàm _CrtDumpMemoryLeaks không hoạt động với thử nghiệm của google vì nó báo cáo sai một số rò rỉ trong khuôn khổ, nhưng giải pháp này tránh được vấn đề đã nói. Bạn phải nhớ tạo một thể hiện của lớp ở trên cùng của mọi trường hợp thử nghiệm. – tathagata

+2

Tôi vừa thêm thành công giải pháp này vào một tập hợp lớn hàng trăm bài kiểm tra. Bạn có thể thêm con trỏ làm biến lớp và phân bổ nó trong TEST_METHOD_INITIALIZE và xóa nó trong TEST_METHOD_CLEANUP. Bằng cách đó nó chỉ là một lần cho mỗi TEST_CLASS – SecsAndCyber

1

Bạn có thể phát hiện rò rỉ bộ nhớ trên các bài kiểm tra bằng cách cung cấp các chức năng mới, xóa, malloc và miễn phí bằng cách thêm thông tin theo dõi bộ nhớ khi cấp phát.

+1

Đây là điều tôi muốn tránh ... – sevaxx

+1

Vâng, bạn sẽ phải cung cấp triển khai thay thế của người cấp phát theo dõi phân bổ. Điều đó không có nghĩa là bạn phải tự viết nó ... –

+0

Đúng vậy. Nhìn trực tuyến cho libs cấp phát. Nedalloc là một trong những tôi biết (Ogre sử dụng nó). Nếu bạn không muốn sử dụng một cái gì đó như thế, sau đó bạn có thể sử dụng một công cụ bên ngoài để thay thế ... nhưng điều đó sẽ không thể sử dụng được trong các bài kiểm tra đơn vị của bạn. Trong phòng thu trực quan có một bộ kiểm tra rò rỉ bộ nhớ cơ bản nhưng nó chỉ tốt để phát hiện rằng có một, không cung cấp luôn luôn thông tin về rò rỉ bộ nhớ ... – Klaim

5

Bạn có thể sử dụng thư viện phân bổ tcmalloc của Google, cung cấp heapchecker.

(Lưu ý heapchecking rằng có thể thêm chi phí đáng kể đến hiệu suất của chương trình của bạn, vì vậy bạn lẽ chỉ muốn kích hoạt nó trên debug xây dựng hoặc kiểm tra đơn vị.)

Và bạn hỏi ví dụ mã, do đó here it is.

+0

Một liên kết có sẵn cho TCMalloc: http: //goog-perftools.sourceforge. net/doc/tcmalloc.html –

1

1) Sau khi một số điều tra của tôi và dựa trên giải pháp rất tốt đẹp (cho Windows) của Chris Becke, tôi đã thực hiện một giải pháp rất giống với hệ điều hành Linux.

2) rò rỉ bộ nhớ của tôi mục tiêu phát hiện:

Are khá rõ ràng - phát hiện rò rỉ trong khi cũng như:

2.1) Lý tưởng nhất là một cách chính xác - chỉ ra chính xác có bao nhiêu byte được phân bổ CHƯA không deallocated.

2.2) Nỗ lực tốt nhất - nếu không chính xác, chỉ ra một cách "dương tính giả" (cho chúng tôi biết về rò rỉ ngay cả khi nó không nhất thiết phải là một và cùng một lúc KHÔNG bỏ lỡ bất kỳ phát hiện rò rỉ nào). Nó là tốt hơn để được khắc nghiệt hơn về bản thân chúng ta ở đây.

2.3) Vì tôi đang viết các bài kiểm tra đơn vị của mình trong khung GTest - hãy kiểm tra mỗi bài kiểm tra đơn vị GTest là "thực thể nguyên tử".

2.4) Cân nhắc việc phân bổ theo kiểu "C" (deallocations) bằng cách sử dụng malloc/miễn phí.

2.5) Lý tưởng nhất - hãy xem xét C++ “tại chỗ phân bổ”.

2.6) Dễ sử dụng và tích hợp vào mã hiện có (các lớp dựa trên GTest để kiểm tra đơn vị).

2.7) Có khả năng “cấu hình” cài đặt kiểm tra chính (bật/tắt kiểm tra bộ nhớ, vv…) cho mỗi bài kiểm tra và/hoặc toàn bộ lớp kiểm tra.

3) Giải pháp kiến ​​trúc:

giải pháp của tôi sử dụng các khả năng thừa hưởng của việc sử dụng khuôn khổ GTest, vì vậy nó định nghĩa một lớp “cơ sở” cho mỗi lớp thử nghiệm đơn vị chúng tôi sẽ bổ sung trong thời gian tới. Về cơ bản, các chức năng chính của lớp cơ sở có thể được chia thành các phần sau:

3.1) Chạy thử nghiệm kiểu GTest “đầu tiên” để hiểu số lượng “bộ nhớ thừa” được cấp phát trên heap trong trường hợp lỗi kiểm tra Như Chris Becke đã đề cập trong câu cuối cùng của câu trả lời ở trên.

3.2) Dễ tích hợp - đơn giản là kế thừa từ lớp cơ sở này và viết các bài kiểm tra đơn vị của bạn “chức năng TEST_F style”.

3.3.1) Đối với mỗi bài kiểm tra, chúng tôi có thể quyết định có nêu rõ cách khác để thực hiện kiểm tra rò rỉ bộ nhớ hay không. Điều này được thực hiện thông qua SetIgnoreMemoryLeakCheckForThisTest() metohd. Lưu ý: Không cần phải "đặt lại" lần nữa - nó sẽ tự động xảy ra cho lần kiểm tra tiếp theo do cách kiểm tra đơn vị GTest hoạt động (chúng gọi Ctor trước cho mỗi cuộc gọi hàm). Ngoài ra, nếu vì lý do nào đó bạn biết trước rằng thử nghiệm của bạn sẽ "bỏ lỡ" một số deallocations của bộ nhớ và bạn biết số tiền - bạn có thể tận dụng lợi thế của hai chức năng để đưa thực tế này vào cân nhắc khi thực hiện kiểm tra bộ nhớ (theo cách này, được thực hiện bằng cách "đơn giản" trừ số lượng bộ nhớ được sử dụng ở đầu bài kiểm tra từ lượng bộ nhớ được sử dụng ở cuối bài kiểm tra).

Dưới đây là các lớp cơ sở tiêu đề:

// memoryLeakDetector.h: 
#include "gtest/gtest.h" 
extern int g_numOfExtraBytesAllocatedByGtestUponTestFailure; 

// The fixture for testing class Foo. 
class MemoryLeakDetectorBase : public ::testing::Test 
{ 
// methods: 
// ------- 
public: 
    void SetIgnoreMemoryLeakCheckForThisTest() { m_ignoreMemoryLeakCheckForThisTest= true; } 
    void SetIsFirstCheckRun() { m_isFirstTestRun = true; } 

protected: 

    // You can do set-up work for each test here. 
    MemoryLeakDetectorBase(); 

    // You can do clean-up work that doesn't throw exceptions here. 
    virtual ~MemoryLeakDetectorBase(); 

    // If the constructor and destructor are not enough for setting up 
    // and cleaning up each test, you can define the following methods: 

    // Code here will be called immediately after the constructor (right 
    // before each test). 
    virtual void SetUp(); 

    // Code here will be called immediately after each test (right 
    // before the destructor). 
    virtual void TearDown(); 

private: 
    void getSmartDiff(int naiveDiff); 
    // Add the extra memory check logic according to our 
    // settings for each test (this method is invoked right 
    // after the Dtor). 
    virtual void PerformMemoryCheckLogic(); 

// members: 
// ------- 
private: 
    bool m_ignoreMemoryLeakCheckForThisTest; 
    bool m_isFirstTestRun; 
    bool m_getSmartDiff; 
    size_t m_numOfBytesNotToConsiderAsMemoryLeakForThisTest; 
    int m_firstCheck; 
    int m_secondCheck; 
}; 

Và đây là nguồn gốc của lớp cơ sở này:

// memoryLeakDetectorBase.cpp 
#include <iostream> 
#include <malloc.h> 

#include "memoryLeakDetectorBase.h" 

int g_numOfExtraBytesAllocatedByGtestUponTestFailure = 0; 

static int display_mallinfo_and_return_uordblks() 
{ 
    struct mallinfo mi; 

    mi = mallinfo(); 
    std::cout << "========================================" << std::endl; 
    std::cout << "========================================" << std::endl; 
    std::cout << "Total non-mmapped bytes (arena):" << mi.arena << std::endl; 
    std::cout << "# of free chunks (ordblks):" << mi.ordblks << std::endl; 
    std::cout << "# of free fastbin blocks (smblks):" << mi.smblks << std::endl; 
    std::cout << "# of mapped regions (hblks):" << mi.hblks << std::endl; 
    std::cout << "Bytes in mapped regions (hblkhd):"<< mi.hblkhd << std::endl; 
    std::cout << "Max. total allocated space (usmblks):"<< mi.usmblks << std::endl; 
    std::cout << "Free bytes held in fastbins (fsmblks):"<< mi.fsmblks << std::endl; 
    std::cout << "Total allocated space (uordblks):"<< mi.uordblks << std::endl; 
    std::cout << "Total free space (fordblks):"<< mi.fordblks << std::endl; 
    std::cout << "Topmost releasable block (keepcost):" << mi.keepcost << std::endl; 
    std::cout << "========================================" << std::endl; 
    std::cout << "========================================" << std::endl; 
    std::cout << std::endl; 
    std::cout << std::endl; 

    return mi.uordblks; 
} 

MemoryLeakDetectorBase::MemoryLeakDetectorBase() 
    : m_ignoreMemoryLeakCheckForThisTest(false) 
    , m_isFirstTestRun(false) 
    , m_getSmartDiff(false) 
    , m_numOfBytesNotToConsiderAsMemoryLeakForThisTest(0) 
{ 
    std::cout << "MemoryLeakDetectorBase::MemoryLeakDetectorBase" << std::endl; 
    m_firstCheck = display_mallinfo_and_return_uordblks(); 
} 

MemoryLeakDetectorBase::~MemoryLeakDetectorBase() 
{ 
    std::cout << "MemoryLeakDetectorBase::~MemoryLeakDetectorBase" << std::endl; 
    m_secondCheck = display_mallinfo_and_return_uordblks(); 
    PerformMemoryCheckLogic(); 
} 

void MemoryLeakDetectorBase::PerformMemoryCheckLogic() 
{ 
    if (m_isFirstTestRun) { 
     std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - after the first test" << std::endl; 
     int diff = m_secondCheck - m_firstCheck; 
     if (diff > 0) { 
      std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - setting g_numOfExtraBytesAllocatedByGtestUponTestFailure to:" << diff << std::endl; 
      g_numOfExtraBytesAllocatedByGtestUponTestFailure = diff; 
     } 
     return; 
    } 

    if (m_ignoreMemoryLeakCheckForThisTest) { 
     return; 
    } 
    std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic" << std::endl; 

    int naiveDiff = m_secondCheck - m_firstCheck; 

    // in case you wish for "more accurate" difference calculation call this method 
    if (m_getSmartDiff) { 
     getSmartDiff(naiveDiff); 
    } 

    EXPECT_EQ(m_firstCheck,m_secondCheck); 
    std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - the difference is:" << naiveDiff << std::endl; 
} 

void MemoryLeakDetectorBase::getSmartDiff(int naiveDiff) 
{ 
    // according to some invastigations and assumemptions, it seems like once there is at least one 
    // allocation which is not handled - GTest allocates 32 bytes on the heap, so in case the difference 
    // prior for any further substrcutions is less than 32 - we will assume that the test does not need to 
    // go over memory leak check... 
    std::cout << "MemoryLeakDetectorBase::getMoreAccurateAmountOfBytesToSubstructFromSecondMemoryCheck - start" << std::endl; 
    if (naiveDiff <= 32) { 
     std::cout << "MemoryLeakDetectorBase::getSmartDiff - the naive diff <= 32 - ignoring..." << std::endl; 
     return; 
    } 

    size_t numOfBytesToReduceFromTheSecondMemoryCheck = m_numOfBytesNotToConsiderAsMemoryLeakForThisTest + g_numOfExtraBytesAllocatedByGtestUponTestFailure; 
    m_secondCheck -= numOfBytesToReduceFromTheSecondMemoryCheck; 
    std::cout << "MemoryLeakDetectorBase::getSmartDiff - substructing " << numOfBytesToReduceFromTheSecondMemoryCheck << std::endl; 
} 

void MemoryLeakDetectorBase::SetUp() 
{ 
    std::cout << "MemoryLeakDetectorBase::SetUp" << std::endl; 
} 

void MemoryLeakDetectorBase::TearDown() 
{ 
    std::cout << "MemoryLeakDetectorBase::TearDown" << std::endl; 
} 

// The actual test of this module: 


TEST_F(MemoryLeakDetectorBase, getNumOfExtraBytesGTestAllocatesUponTestFailureTest) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - START" << std::endl; 

    // Allocate some bytes on the heap and DO NOT delete them so we can find out the amount 
    // of extra bytes GTest framework allocates upon a failure of a test. 
    // This way, upon our legit test failure, we will be able to determine of many bytes were NOT 
    // deleted EXACTLY by our test. 

    std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - size of char:" << sizeof(char) << std::endl; 
    char* pChar = new char('g'); 
    SetIsFirstCheckRun(); 
    std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - END" << std::endl; 
} 

Cuối cùng, một mẫu "GTest dựa trên" đơn vị lớp thử nghiệm có sử dụng này các lớp cơ sở và minh họa các tập quán và một số POC khác nhau (bằng chứng khái niệm) cho tất cả các loại phân bổ và xác minh khác nhau nếu chúng ta có thể (hoặc không) phát hiện các phân bổ đã bỏ qua.

// memoryLeakDetectorPocTest.cpp 
#include "memoryLeakDetectorPocTest.h" 
#include <cstdlib> // for malloc 

class MyObject 
{ 

public: 
    MyObject(int a, int b) : m_a(a), m_b(b) { std::cout << "MyObject::MyObject" << std::endl; } 
    ~MyObject() { std::cout << "MyObject::~MyObject" << std::endl; } 
private: 
    int m_a; 
    int m_b; 
}; 

MemoryLeakDetectorPocTest::MemoryLeakDetectorPocTest() 
{ 
    std::cout << "MemoryLeakDetectorPocTest::MemoryLeakDetectorPocTest" << std::endl; 
} 

MemoryLeakDetectorPocTest::~MemoryLeakDetectorPocTest() 
{ 
    std::cout << "MemoryLeakDetectorPocTest::~MemoryLeakDetectorPocTest" << std::endl; 
} 

void MemoryLeakDetectorPocTest::SetUp() 
{ 
    std::cout << "MemoryLeakDetectorPocTest::SetUp" << std::endl; 
} 

void MemoryLeakDetectorPocTest::TearDown() 
{ 
    std::cout << "MemoryLeakDetectorPocTest::TearDown" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForNativeType) 
{ 

    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeType - START" << std::endl; 

    // allocate some bytes on the heap and intentially DONT release them... 
    const size_t numOfCharsOnHeap = 23; 
    std::cout << "size of char is:" << sizeof(char) << " bytes" << std::endl; 
    std::cout << "allocating " << sizeof(char) * numOfCharsOnHeap << " bytes on the heap using new []" << std::endl; 
    char* arr = new char[numOfCharsOnHeap]; 

    // DO NOT delete it on purpose... 
    //delete [] arr; 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForUserDefinedType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedType - START" << std::endl; 

    std::cout << "size of MyObject is:" << sizeof(MyObject) << " bytes" << std::endl; 
    std::cout << "allocating MyObject on the heap using new" << std::endl; 
    MyObject* myObj1 = new MyObject(12, 17); 

    delete myObj1; 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyMallocAllocationForNativeType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - START" << std::endl; 
    size_t numOfDoublesOnTheHeap = 3; 
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - sizeof double is " << sizeof(double) << std::endl; 
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - allocaitng " << sizeof(double) * numOfDoublesOnTheHeap << " bytes on the heap" << std::endl; 
    double* arr = static_cast<double*>(malloc(sizeof(double) * numOfDoublesOnTheHeap)); 

    // NOT free-ing them on purpose !! 
    // free(arr); 
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForNativeSTLVectorType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeSTLVectorType - START" << std::endl; 
    std::vector<int> vecInt; 
    vecInt.push_back(12); 
    vecInt.push_back(15); 
    vecInt.push_back(17); 

    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeSTLVectorType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForUserDefinedSTLVectorType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedSTLVectorType - START" << std::endl; 
    std::vector<MyObject*> vecMyObj; 
    vecMyObj.push_back(new MyObject(7,8)); 
    vecMyObj.push_back(new MyObject(9,10)); 

    size_t vecSize = vecMyObj.size(); 
    for (int i = 0; i < vecSize; ++i) { 
     delete vecMyObj[i]; 
    } 

    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedSTLVectorType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyInPlaceAllocationAndDeAllocationForUserDefinedType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationAndDeAllocationForUserDefinedType - START" << std::endl; 
    void* p1 = malloc(sizeof(MyObject)); 
    MyObject *p2 = new (p1) MyObject(12,13); 

    p2->~MyObject(); 
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationAndDeAllocationForUserDefinedType - END" << std::endl; 
} 

TEST_F(MemoryLeakDetectorPocTest, verifyInPlaceAllocationForUserDefinedType) 
{ 
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationForUserDefinedType - START" << std::endl; 
    void* p1 = malloc(sizeof(MyObject)); 
    MyObject *p2 = new (p1) MyObject(12,13); 

    // Dont delete the object on purpose !! 
    //p2->~MyObject(); 
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationForUserDefinedType - END" << std::endl; 
} 

File header của lớp này:

// memoryLeakDetectorPocTest.h 
#include "gtest/gtest.h" 
#include "memoryLeakDetectorBase.h" 

// The fixture for testing class Foo. 
class MemoryLeakDetectorPocTest : public MemoryLeakDetectorBase 
{ 
protected: 

    // You can do set-up work for each test here. 
    MemoryLeakDetectorPocTest(); 

    // You can do clean-up work that doesn't throw exceptions here. 
    virtual ~MemoryLeakDetectorPocTest(); 

    // Code here will be called immediately after the constructor (right 
    // before each test). 
    virtual void SetUp(); 

    // Code here will be called immediately after each test (right 
    // before the destructor). 
    virtual void TearDown(); 
}; 

Hy vọng nó sẽ là hữu ích và xin vui lòng cho tôi biết nếu có bất cứ điều gì mà không phải là rõ ràng.

Chúc mừng,

Guy.

+1

OK khi tràn ngăn xếp - thậm chí được khuyến khích, để đặt câu hỏi và sau đó tự trả lời, trong trường hợp này tôi sẽ đề xuất câu trả lời này bao gồm một kịch bản khác so với câu hỏi gốc (và các thẻ của nó) –

Các vấn đề liên quan