Domanda

Esiste un buon modo per testare i distruttori di unità? Come dire che ho una lezione come questa (inventata) esempio:

class X
{
private:
    int *x;

public:
    X()
    {
         x = new int;
    }

    ~X()
    {
         delete x;
    }

    int *getX() {return x;}
    const int *getX() const {return x;}
};

C'è un buon modo per testare l'unità per assicurarsi che x venga cancellato senza ingombrare il mio file hpp con #ifdef TEST o rompere l'incapsulamento? Il problema principale che sto vedendo è che è difficile dire se x sia stato davvero cancellato, soprattutto perché l'oggetto non rientra nell'ambito nel momento in cui viene chiamato il distruttore.

È stato utile?

Soluzione

Potrebbe esserci qualcosa da dire per l'iniezione di dipendenza. Invece di creare un oggetto (in questo caso un int, ma in un caso non forzato più probabilmente un tipo definito dall'utente) nel suo costruttore, l'oggetto viene passato come parametro al costruttore. Se l'oggetto viene creato in un secondo momento, una factory viene passata al costruttore di X.

Quindi, quando si esegue il test unitario, si passa un oggetto simulato (o una fabbrica simulata che crea oggetti simulati) e il distruttore registra il fatto che è stato chiamato. Il test fallisce se non lo è.

Ovviamente non puoi deridere (o altrimenti sostituire) un tipo incorporato, quindi in questo caso particolare non va bene, ma se definisci l'oggetto / fabbrica con un'interfaccia allora puoi.

Il controllo delle perdite di memoria nei test unitari può spesso essere eseguito a un livello superiore, come altri hanno già detto. Ma ciò verifica solo che sia stato chiamato un distruttore, non prova che sia stato chiamato il giusto distruttore. Quindi non es. trova un "virtuale" mancante dichiarazione sul distruttore del tipo del membro x (di nuovo, non pertinente se si tratta solo di un int).

Altri suggerimenti

Penso che il tuo problema sia che il tuo esempio attuale non sia testabile. Dato che vuoi sapere se x è stato eliminato, devi davvero essere in grado di sostituire x con una simulazione. Questo è probabilmente un po 'OTT per un int ma immagino che nel tuo vero esempio tu abbia qualche altra classe. Per renderlo testabile, il costruttore X deve richiedere l'oggetto che implementa l'interfaccia int :

template<class T>
class X
{
  T *x;
  public:
  X(T* inx)
    : x(inx)
  {
  }

  // etc
};

Ora diventa semplice deridere il valore per x e il finto può gestire il controllo per la corretta distruzione.

Non prestare attenzione alle persone che affermano che dovresti interrompere l'incapsulamento o ricorrere a hack orribili per ottenere codice testabile. Mentre è vero che il codice testato è migliore del codice non testato, il codice testabile è il migliore di tutti e si traduce sempre in un codice più chiaro con meno hack e accoppiamento inferiore.

Tendo ad andare con un " Con ogni mezzo necessario " approccio ai test. Se ha bisogno di un test, sono disposto a perdere le astrazioni, rompere l'incapsulamento e l'hacking ... perché il codice testato è meglio del bel codice. Spesso chiamerò i metodi che suddividono qualcosa come VaildateForTesting o OverrideForTesting per chiarire che la violazione dell'incapsulamento è intesa solo per il test.

Non conosco altro modo per farlo in C ++ se non avere il distruttore che chiama un singleton per registrare che è stato distrutto. Ho escogitato un metodo per fare qualcosa di simile a questo in C # usando un riferimento debole (non viola l'incapsulamento o le astrazioni con questo approccio). Non sono abbastanza creativo da inventare un'analogia con il C ++, ma TU potresti esserlo. Se aiuta, grande, in caso contrario, scusa.

http://houseofbilz.com/archive/2008/11/11/writing-tests-to-catch-memory-leaks-in-.net.aspx

Nell'esempio, definisci e gestisci il tuo nuovo globale ed elimina.

Per evitare #ifdefs, ho stretto amicizia con le classi di test. È possibile impostare / salvare / ottenere lo stato richiesto per verificare i risultati di una chiamata.

Non sarà rilevante per l'uomo che ha posto la domanda, ma potrebbe essere utile per gli altri che lo leggono. Mi è stata posta una domanda simile in un colloquio di lavoro.

Supponendo che la memoria sia limitata, puoi provare questo metodo:

  1. Allocare memoria fino a quando l'allocazione non riesce con messaggio di memoria esaurita (prima di eseguire qualsiasi test rilevante sul distruttore) e salvare la dimensione della memoria disponibile prima di eseguire il test.
  2. Esegui il test (chiama il costruttore ed esegui alcune azioni come desideri sulla nuova istanza).
  3. Esegui il distruttore.
  4. Eseguire nuovamente la parte di allocazione (come nel passaggio 1) se riesci a allocare esattamente la stessa memoria che riesci a allocare prima di eseguire il test, il distruttore funziona bene.

Questo metodo funziona (ragionevolmente) quando hai poca memoria limitata, altrimenti sembra irragionevole, almeno per la mia opinione.

Alcuni compilatori sovrascriveranno la memoria eliminata con un modello noto in modalità debug per aiutare a rilevare l'accesso a puntatori pendenti. So che Visual C ++ usava 0xDD, ma non lo uso da un po '.

Nel tuo caso di test, puoi archiviare una copia di x, lasciarlo fuori dal campo di applicazione e assicurarti che * x == 0xDDDDDDDDD:

void testDestructor()
{
    int *my_x;
    {
        X object;
        my_x = object.getX();
    }
    CPPUNIT_ASSERT( *my_x == 0xDDDDDDDD );
}

Non è un suggerimento agnostico sulla piattaforma, ma in passato ho effettuato chiamate alle funzioni di controllo dell'heap del CRT durante i test delle unità, per verificare che non ci sia più memoria allocata alla fine di un test (o forse un intero set di test ) rispetto all'inizio. Potresti anche essere in grado di fare qualcosa di simile con la strumentazione della tua piattaforma, controllare il numero di handle, ecc.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top