Come passare una simulazione come std :: unique_ptr alla classe in prova

1

Sto scrivendo alcune unità di test usando googletest e googlemock e sono bloccato in un problema relativo ai puntatori e al polimorfismo di C ++ 11.

Supponi di avere queste classi:

class A {
public:
    virtual void doThings() {...};
};

class B {
private:
    B(std::unique_ptr<A> a): a_(std::move(a)) {}
    std::unique_ptr<A> a_;
};

Voglio testare la classe B e quindi creo un mock per A e lo passo nel costruttore:

class MockA: public A {
public:
    virtual ~MockA() {}
    MOCK_METHOD0(doThings, void());
};

TEST(...) {
    auto ma = std::make_unique<MockA>();
    B b(std::move(ma));

    // Set expectation on mock

    // call B method
}

Il problema è questo:

  • il mock è stato spostato all'interno dell'istanza B e quindi la verifica delle aspettative genera un'eccezione perché ma è nullo.

Il mio primo tentativo di risolverlo è stato quello di modificare B come segue:

class B {
private:
    B(std::unique_ptr<A>& a): a_(a) {}
    std::unique_ptr<A>& a_;
};

Ora B gestisce il puntatore univoco riferimenti e non usa std::move . Anche il test è cambiato (non più uso di std::move ):

TEST(...) {
    auto ma = std::make_unique<MockA>();
    B b(ma);

    // Set expectation on mock

    // call B method
}

Ora il codice non viene compilato perché i riferimenti (se ho capito correttamente l'errore) non sono polimorfici (il puntatore univoco di MockA non può essere convertito nel puntatore univoco di A ).

Mi mancano alcune nozioni di base sui puntatori intelligenti? O è questo il comportamento previsto con i puntatori unici e quindi devo ripensare alle mie classi (magari usando i puntatori condivisi)?

    
posta Marco Stramezzi 17.10.2017 - 16:22
fonte

2 risposte

4

Il modo più semplice sarebbe mantenere la classe B esattamente come prima, ma prendi il puntatore da "ma" prima di passarlo a "b" nel caso di test. Piace così ...

TEST(...){
    auto ma = std::make_unique<MockA>();
    //this will be valid even after the current unique_ptr transfers ownership
    //It will be invalidated, however, if a smart pointer deletes it
    auto ( or MockA*) rawPtr = ma.get();
    B b(ma);

    //ma now points to NULL, but rawptr is still valid
    //Run analysis on mock via rawPtr
}

Cambiare la firma di B in questo modo non sarebbe saggio. Ricorda, se vuoi che una classe abbia la proprietà esclusiva di un puntatore, dovrebbe avere il suo unique_ptr per quel puntatore. Se si modifica l'opzione unique_ptr di B su un riferimento, B non è più tecnicamente proprietario del puntatore anche se è probabile che abbia scritto la classe in modo tale che agisca come se fosse il proprietario del puntatore. Ciò potrebbe portare a comportamenti / arresti anomali davvero strani.

Se ci si aspetta che sia B che A abbiano la proprietà del puntatore, il che significa che il puntatore deve essere valido mentre B e un'altra classe / funzione sono vivi / in esecuzione, allora si dovrebbe usare un parametro shared_ptr. Se il puntatore deve essere valido solo mentre SOLO B è vivo, dovresti usare un unique_ptr e renderlo membro di B.

Senza vedere il resto del tuo codice, non posso dire con certezza assoluta, ma sono abbastanza sicuro di voler continuare ad usare un unique_ptr come membro di B in questo caso.

Modifica

Come ha fatto notare Caleth, l'intero scopo di B che ha un unique_ptr è che ha la proprietà del puntatore. Quindi se B decide che è il momento di eliminare il ptr, quella memoria non è più valida e qualsiasi tentativo di accedervi tramite rawPtr porterà a cose molto brutte (comportamento non definito, arresti anomali, ecc.)

Fai questo solo se sai come B gestisce il unique_ptr. Accede solo a rawPtr fino a quando B cancella il ptr o consente a unique_ptr di uscire dall'ambito.

    
risposta data 17.10.2017 - 17:18
fonte
3

Per cominciare, la risposta di Ryan è giustificabile, perché la simulazione viene utilizzata in un ambiente di test white-box, il che significa che il programmatore può vedere tutto il codice sorgente e quindi sa esattamente come violare la proprietà del puntatore intelligente e si assicura che il codice funzionerà correttamente.

Se vi è la necessità per due oggetti di condividere i dati in un disposizione "Dead drop" , ecco uno schema che illustra la configurazione:

Schema illustrando due oggetti che condividono i dati tramite una disposizione

Lo "spymaster" sarebbe il codice di test unitario, che controlla tutto. Un'istanza di "informant" (il "dead drop") viene creata come std::shared_ptr . Questo informatore avrà metodi di settaggio che saranno chiamati dal "traditore" (per ricevere i dati), e avrà metodi getter in modo che i suoi dati possano essere recuperati dal " spymaster".

Il "spymaster" istanzia il "traditore" come std::unique_ptr e lo passa al "clueless" (il code-under-test). Una volta consegnato, lo "spymaster" non avrà accesso diretto al "traditore". Invece, interagirà con il codice sotto test, e quando l'interazione termina recupererà i dati dal "informatore" o dal "dead drop".

    
risposta data 17.10.2017 - 22:14
fonte

Leggi altre domande sui tag