我試圖爲我的代碼實現單元測試,而且我很難做到這一點。C++ - 是否有可能在單元測試中實現內存泄漏測試?
理想情況下,我想測試一些類不僅爲了良好的功能,而且爲了正確的內存分配/釋放。我不知道這個檢查是否可以使用單元測試框架完成。我正在使用Visual Assert順便說一句。如果可能,我希望看到一些示例代碼!
我試圖爲我的代碼實現單元測試,而且我很難做到這一點。C++ - 是否有可能在單元測試中實現內存泄漏測試?
理想情況下,我想測試一些類不僅爲了良好的功能,而且爲了正確的內存分配/釋放。我不知道這個檢查是否可以使用單元測試框架完成。我正在使用Visual Assert順便說一句。如果可能,我希望看到一些示例代碼!
只要您的單元測試使用調試c運行時運行,您可以直接在dev studio中使用調試功能來執行泄漏檢查。
一個簡單的例子會是這個樣子:
#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);
}
};
,並使用它在一個單元測試:
UNIT_TEST(blah)
{
CrtCheckMemory check;
// TODO: add the unit test here
}
一些單元測試框架做出自己的分配 - 例如分配塊谷歌的當單元測試失敗時,任何因任何其他原因而出現故障的測試塊總會出現誤報「泄漏」。
通過添加關於分配的內存跟蹤信息,您可以通過提供您自己的新,刪除,malloc和免費函數的實現來檢測內存泄漏。
您可以使用谷歌的tcmalloc分配庫,它提供了一個heapchecker。
(請注意,heapchecking可以明顯的開銷添加到您的程序的性能,所以你可能只想在調試版本或單元測試來啓用它。)
而且你問的示例代碼,所以here it is。
TCMalloc的可用鏈接:http://goog-perftools.sourceforge。 net/doc/tcmalloc.html – 2017-06-22 16:32:40
1)後,我的一些調查,並根據克里斯·貝克的非常好的解決方案(適用於Windows),我已經爲Linux操作系統非常類似的解決方案。
2)我的內存泄漏檢測目標:
是相當明確的 - 檢測泄漏,同時還有:
2.1)理想的情況下以精確的方式 - 表明有多少字節究竟是如何被分配YET不釋放。
2.2)盡力而爲 - 如果不完全,表明在「假陽性」的方式(告訴我們關於泄漏,即使它不一定是一個,並在同一時間,不要錯過任何泄漏檢測)。在這裏對自己更加苛刻最好。
2.3)因爲我在GTEST框架寫我的單元測試 - 測試每個GTEST單元測試作爲「原子實體」。 2.4)使用malloc/free也考慮「C-style」分配(釋放)。
2.5)理想情況下 - 考慮C++「就地分配」。
2.6)易於使用和集成到現有的代碼(單元測試基於GTest的類)。
2。7)能夠爲每個測試和/或整個測試等級「配置」主要檢查設置(啓用/禁用內存檢查等)。
3)解決方案架構:
我的解決方案使用使用GTEST框架的遺傳功能,所以它定義爲每個單元測試類,我們將在未來添加一個「基地」類。基本上,基類的主要功能可以分爲以下幾種:
3.1)運行「第一」GTest風格測試,以便了解在測試失敗的情況下分配在堆上的「額外內存」的數量正如克里斯貝克在上面的答案的最後一句中提到的那樣。 3.2)易於集成 - 簡單地繼承這個基類並編寫你的單元測試「TEST_F風格」函數。
3.3.1)對於每個測試,我們可以決定是否聲明否則執行內存泄漏檢查或不。這是通過SetIgnoreMemoryLeakCheckForThisTest()metohd完成的。 注意:無需再次「重置」它 - 由於GTest單元測試的工作方式(它們在每次函數調用之前它們先調用Ctor),它會在下一次測試時自動發生。另外,如果你由於某種原因事先知道你的測試會「錯過」一些內存釋放,並且你知道這個數量 - 你可以利用這兩個函數來把這個事實轉化爲一次執行內存檢查時的考慮因素(順便說一句,通過「簡單地」從測試結束時減去測試開始時使用的內存量來執行)。
下面是報頭的基類:
// 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;
};
這裏是這個基類的來源:
// 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;
}
最後,樣品「基於GTEST-」的單元測試類使用此基類,並說明如果我們能夠(或不能)檢測到錯過的解除分配,用法和幾種不同的POC(概念證明)到各種不同的分配和驗證。
// 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;
}
這個類的頭文件:
// 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();
};
希望這是有益的,請讓我知道如果有什麼是不明確的。
乾杯,
蓋伊。
堆棧溢出 - 甚至鼓勵,可以問一個問題,然後自己回答它,這種情況下,我會推薦它,因爲這個答案涵蓋了不同於原始問題解決方案(和它的標籤) – 2017-06-26 06:08:48
這是一個非常「乾淨」的解決方案。我試了一下(在一個公認的簡單情況下),它按預期工作。 +1 – sevaxx 2010-06-06 11:07:27
我會第二個。這是一個非常乾淨的解決方案。只是調用_CrtDumpMemoryLeaks函數不適用於谷歌測試,因爲它錯誤地報告了框架中的一些泄漏,但是這個解決方案避免了上述問題。儘管如此,你必須記得在每個測試用例的頂部創建一個類的實例。 – tathagata 2010-07-29 11:30:37
我剛成功地將這個解決方案添加到了大量的數百個測試中。您可以將指針添加爲類變量,並將其分配到TEST_METHOD_INITIALIZE中,並在TEST_METHOD_CLEANUP中將其刪除。這樣,它只是每TEST_CLASS – SecsAndCyber 2015-09-17 20:42:50