2010-06-05 10 views
15

Sto provando a implementare il test delle unità per il mio codice e sto facendo fatica a farlo.C++ - È possibile implementare il test di perdita di memoria in un test di unità?

Idealmente mi piacerebbe testare alcune classi non solo per una buona funzionalità ma anche per un'adeguata allocazione/deallocazione della memoria. Mi chiedo se questo controllo può essere fatto utilizzando un framework di test unitario. Sto usando Visual Assert btw. Mi piacerebbe vedere qualche codice di esempio, se possibile!

risposta

13

È possibile utilizzare la funzionalità di debug direttamente in dev studio per eseguire il controllo delle perdite, a condizione che l'unità esegua il test utilizzando il debug c-runtime.

Un semplice esempio potrebbe essere simile a questa:

#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); 
    } 
}; 

e di utilizzarlo in una prova di unità:

framework di test
UNIT_TEST(blah) 
{ 
    CrtCheckMemory check; 

    // TODO: add the unit test here 

} 

Alcune unità di prendere le proprie allocazioni - Google, ad esempio alloca blocchi quando un test unitario fallisce, quindi ogni blocco di test che ha esito negativo per qualsiasi altro motivo ha sempre anche una "perdita" di falsi positivi.

+2

Questa è una soluzione molto "pulita". L'ho provato (in un caso certamente semplice) e ha funzionato come previsto. +1 – sevaxx

+1

Lo metterò in secondo piano. Questa è una soluzione davvero pulita.La semplice chiamata della funzione _CrtDumpMemoryLeaks non funziona con google test perché segnala erroneamente alcune perdite nel framework, ma questa soluzione evita il suddetto problema. Devi però ricordarti di creare un'istanza della classe all'inizio di ogni test case. – tathagata

+2

Ho appena aggiunto questa soluzione a un ampio insieme di centinaia di test. È possibile aggiungere un puntatore come variabile di classe e allocarlo in TEST_METHOD_INITIALIZE ed eliminarlo in TEST_METHOD_CLEANUP. In questo modo è solo una volta per TEST_CLASS – SecsAndCyber

1

Potrebbe essere possibile rilevare perdite di memoria durante i test fornendo la propria implementazione di funzioni nuove, di eliminazione, malloc e gratuite, aggiungendo informazioni di tracciamento della memoria sull'assegnazione.

+1

Questo è qualcosa che vorrei evitare ... – sevaxx

+1

Bene, si dovrà fornire un'implementazione alternativa dell'allocatore che tiene traccia delle allocazioni. Ciò non significa che devi scriverlo da solo ... –

+0

Esatto. Cerca online le librerie di allocatori. Nedalloc è uno che conosco (Ogre lo usa). Se non vuoi usare qualcosa del genere, puoi utilizzare invece uno strumento esterno ... ma non sarà utilizzabile nei test di unità. In Visual Studio è presente un controllo di perdita di memoria di base, ma è bene rilevare che ce n'è uno, non fornendo sempre informazioni sulle perdite di memoria ... – Klaim

5

È possibile utilizzare la libreria di allocazione tcmalloc di Google, che fornisce uno heapchecker.

(Si noti che heapchecking possono aggiungere sovraccarico notevole alle prestazioni del tuo programma, in modo da probabilmente desideri solo per consentirgli il build di debug o test di unità.)

E lei ha chiesto per esempio di codice, così here it is.

+0

Un collegamento disponibile per TCMalloc: http: //goog-perftools.sourceforge. net/doc/tcmalloc.html –

1

1) Dopo alcune ricerche e basato su una soluzione molto bella (per Windows) di Chris Becke, ho realizzato una soluzione molto simile per Linux OS.

2) I miei obiettivi di perdita di memoria di rilevamento:

sono abbastanza chiare - rilevare le perdite, mentre pure:

2.1) Idealmente in modo preciso - indicano esattamente quanti byte sono stati assegnati ma non deallocate.

2.2) Massimo sforzo - se non esattamente, indicare in modo "falso positivo" (comunicarci una perdita anche se non è necessariamente una e allo stesso tempo NON PERDERE alcun rilevamento di perdite). È meglio essere più severi con noi stessi qui.

2.3) Poiché sto scrivendo i miei test unitari nel framework GTest, testare ogni test unitario GTest come "entità atomica".

2.4) Prendere in considerazione anche le allocazioni "C-style" (deallocations) utilizzando malloc/free.

2.5) Idealmente, prendere in considerazione il C++ "allocazioni sul posto".

2.6) Facile da utilizzare e integrare in un codice esistente (classi basate su GTest per il test dell'unità).

2.7) Avere la possibilità di "configurare" le impostazioni dei controlli principali (abilitare/disabilitare il controllo della memoria, ecc.) Per ogni test e/o tutta la classe di test.

3) Architettura Soluzione:

mia soluzione utilizza le funzionalità ereditati utilizzando il framework GTEST, quindi definisce una classe “base” per ogni classe di unit test l'aggiungeremo in futuro. Fondamentalmente, le funzionalità principali della classe base possono essere suddivise in:

3.1) Eseguire il "primo" test di stile GTest per comprendere la quantità di "memoria extra" allocata nello heap in caso di errore di test . Come Chris Becke menzionato nell'ultima frase della sua risposta sopra.

3.2) Facile da integrare - eredita semplicemente da questa classe di base e scrive le tue funzioni di "TEST_F style".

3.3.1) Per ciascun test, è possibile decidere se indicare altrimenti eseguire il controllo di perdita di memoria o meno. Questo viene eseguito tramite il set di memoria SetIgnoreMemoryLeakCheckForThisTest(). Nota: non c'è bisogno di "resettarlo" di nuovo - avverrà automaticamente per il prossimo test a causa del modo in cui i test delle unità GTest funzionano (chiamano il Ctor prior per ogni chiamata di funzione).

3.3.2) Inoltre, se per qualche motivo si conosce in anticipo che il test "mancherà" alcune allocazioni di memoria e si conosce l'importo - è possibile usufruire delle due funzioni per portare questo fatto in considerazioni una volta eseguita la verifica della memoria (che, a proposito, eseguita "semplicemente" sottraendo la quantità di memoria in uso all'inizio del test dalla quantità di memoria in uso alla fine del test).

seguito è la classe di base intestazione:

// 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; 
}; 

E qui è la fonte di questa classe di base:

// 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; 
} 

Infine, un campione "basata GTEST" classe unit test che utilizza questo basa le classi e illustra gli usi e diversi POC (proof of concept) diversi per ogni tipo di allocazioni e verifiche diverse se siamo in grado (o meno) di rilevare le mancate allocazioni.

// 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; 
} 

Il file di intestazione di questa classe:

// 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(); 
}; 

spero che sia utile e vi prego di farmi sapere se c'è qualcosa che non è chiaro.

Cheers,

Guy.

+1

Va bene allo stack overflow - anche incoraggiato, per fare una domanda e poi rispondere da solo, in questo caso lo consiglierei in quanto questa risposta copre uno scenario diverso da quello affrontato dalla domanda originale (e i suoi tag) –

Problemi correlati