Metodo privato di unit test in c ++ usando una classe di amici


15

So che questa è una pratica dibattuta, ma supponiamo che questa sia l'opzione migliore per me. Mi chiedo quale sia la tecnica effettiva per farlo. L'approccio che vedo è questo:

1) Fai una classe di amici quella della classe che è il metodo che voglio testare.

2) Nella classe friend, crea un metodo pubblico che chiama i metodi privati ​​della classe testata.

3) Testare i metodi pubblici della classe di amici.

Ecco un semplice esempio per illustrare i passaggi precedenti:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

Modificare:

Vedo che nella discussione che segue una delle risposte le persone si chiedono quale sia la mia base di codice.

La mia classe ha metodi che sono chiamati da altri metodi; nessuno di questi metodi dovrebbe essere chiamato al di fuori della classe, quindi dovrebbe essere privato. Ovviamente potrebbero essere inseriti in un metodo, ma logicamente sono molto meglio separati. Questi metodi sono abbastanza complicati da giustificare i test unitari e, a causa di problemi di prestazioni, molto probabilmente dovrò ripensare questi metodi, quindi sarebbe bello fare un test per assicurarsi che il mio re-factoring non abbia rotto nulla. Non sono l'unico a lavorare nel team, anche se sono l'unico a lavorare a questo progetto, inclusi i test.

Detto questo, la mia domanda non riguardava se sia una buona pratica scrivere test unitari per metodi privati, anche se apprezzo il feedback.


5
Ecco un suggerimento discutibile per una domanda discutibile. Non mi piace l'accoppiamento di un amico perché il codice rilasciato deve sapere del test. La risposta di Nir qui sotto è un modo per alleviarlo, ma ancora non mi piace molto cambiare la classe per conformarmi al test. Dato che non faccio affidamento sull'ereditarietà spesso, a volte faccio semplicemente proteggere i metodi altrimenti privati ​​e ho una classe di test che eredita ed espone secondo necessità. Mi aspetto almeno tre "fischi" per questo commento, ma la realtà è che l'API pubblica e l'API di test possono differire ed essere ancora distinte dall'API privata. Meh.
J Trana,

4
@JTrana: Perché non scriverlo come una risposta corretta?
Bart van Ingen Schenau,

Ok. Questo non è uno di quelli di cui ti senti super orgoglioso, ma speriamo che ti aiuti.
J Trana,

Risposte:


23

Un'alternativa all'amico (beh, in un certo senso) che uso frequentemente è un modello che ho conosciuto come access_by. È abbastanza semplice:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

Supponiamo ora che la classe B sia coinvolta nel test A. Puoi scrivere questo:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

È quindi possibile utilizzare questa specializzazione di access_by per chiamare i metodi privati ​​di A. Fondamentalmente, ciò che fa è mettere l'onere di dichiarare l'amicizia nel file di intestazione della classe che vuole chiamare i metodi privati ​​di A. Ti consente anche di aggiungere amici ad A senza cambiare la fonte di A. Idiomaticamente, indica anche a chiunque legga la fonte di A che A non indica B un vero amico nel senso di estendere la sua interfaccia. Piuttosto, l'interfaccia di A è completa come indicato e B ha bisogno di un accesso speciale ad A (il test è un buon esempio, ho anche usato questo modello durante l'implementazione dei collegamenti boost python, a volte una funzione che deve essere privata in C ++ è utile per esporre nel layer Python per l'implementazione).


Curioso di un valido caso d'uso per rendere il friend access_by, non è il primo non amico sufficiente - essendo una struttura annidata avrebbe accesso a tutto ciò che è in A? per esempio. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
tangy

10

Se è difficile da testare, è scritto male

Se hai una classe con metodi privati ​​abbastanza complessi da giustificare il proprio test, la classe sta facendo troppo. All'interno c'è un'altra classe che cerca di uscire.

Estrarre i metodi privati ​​che si desidera testare in una nuova classe (o classi) e renderli pubblici. Prova le nuove classi.

Oltre a semplificare il test del codice, questo refactoring renderà il codice più semplice da comprendere e mantenere.


1
Sono completamente d'accordo con questa risposta, se non riesci a testare completamente i tuoi metodi privati ​​testando i metodi pubblici, qualcosa non è giusto e rifattorizzare i metodi privati ​​sulla propria classe sarebbe una buona soluzione.
David Perfors,

4
Nel mio codebase, una classe ha un metodo molto complesso che inizializza un grafico computazionale. Chiama diverse sotto-funzioni in sequenza per raggiungere vari aspetti di questo. Ogni sottofunzione è piuttosto complicata e la somma totale del codice è molto complicata. Tuttavia, le sotto-funzioni sono prive di significato se non richiamate su questa classe e nella giusta sequenza. L'utente si preoccupa solo che il grafico computazionale sia completamente inizializzato; gli intermedi sono inutili per l'utente. Per favore, mi piacerebbe sapere come dovrei refactoring questo e perché ha più senso che semplicemente testare i metodi privati.
Nir Friedman,

1
@Nir: Fai il banale: estrai una classe con tutti quei metodi pubblici e rendi la tua classe esistente una facciata attorno alla nuova classe.
Kevin Cline,

Questa risposta è corretta ma dipende in realtà dai dati con cui stai lavorando. Nel mio caso non mi vengono forniti dati di test effettivi, quindi devo crearne alcuni osservando i dati in tempo reale e quindi "iniettarli" nella mia applicazione e vedere come Un singolo dato è troppo complesso da gestire, quindi sarebbe molto più semplice creare artificialmente dati di test parziali che sono solo un sottoinsieme dei dati in tempo reale da indirizzare a ciascuno piuttosto che riprodurre i dati in tempo reale. Le funzioni private non sono complesse abbastanza da richiedere l'implementazione di molte altre piccole classi (con le rispettive funzionalità)
rbaleksandar

4

Non dovresti testare metodi privati. Periodo. Le classi che usano la tua classe si preoccupano solo dei metodi che fornisce, non di quelli che usa sotto il cofano per funzionare.

Se ti preoccupi della copertura del codice, devi trovare configurazioni che ti consentano di testare quel metodo privato da una delle chiamate al metodo pubblico. Se non riesci a farlo, qual è il punto di avere il metodo in primo luogo? È semplicemente un codice irraggiungibile.


6
Il punto dei metodi privati ​​è facilitare lo sviluppo (attraverso la separazione delle preoccupazioni, o il mantenimento della DEUMIDIFICAZIONE, o un numero qualsiasi di cose), ma sono destinati a essere non permanenti. Sono privati ​​per questo motivo. Potrebbero apparire, scomparire o cambiare drasticamente le funzionalità da un'implementazione alla successiva, motivo per cui legarli ai test unitari non è sempre pratico, o addirittura utile.
Ampt

8
Potrebbe non essere sempre pratico o utile, ma è molto lontano dal dire che non dovresti mai metterli alla prova. Stai parlando dei metodi privati ​​come se fossero i metodi privati ​​di qualcun altro; "potrebbero apparire, scomparire ...". No, non potevano. Se li stai testando direttamente sull'unità, dovrebbe essere solo perché li mantieni da solo. Se si modifica l'implementazione, si modificano i test. In breve, la tua dichiarazione coperta è ingiustificata. Anche se è bene avvisare l'OP di questo, la sua domanda è ancora giustificata e la tua risposta in realtà non risponde.
Nir Friedman,

2
Lasciatemi anche notare: l'OP ha detto in anticipo che è consapevole che si tratta di una pratica dibattuta. Quindi, se vuole farlo comunque, forse ha davvero delle buone ragioni? Nessuno di noi conosce i dettagli della sua base di codice. Nel codice con cui lavoro, abbiamo alcuni programmatori molto esperti ed esperti, e hanno pensato che in alcuni casi fosse utile testare unitamente metodi privati.
Nir Friedman,

2
-1, una risposta del tipo "Non dovresti testare metodi privati". IMHO non è utile. L'argomento su quando testare e quando non testare metodi privati ​​è stato sufficientemente discusso su questo sito. L'OP ha dimostrato di essere a conoscenza di questa discussione, ed è chiaramente alla ricerca di soluzioni partendo dal presupposto che nel suo caso testare metodi privati ​​è la strada da percorrere.
Doc Brown,

3
Penso che il problema qui sia che questo potrebbe essere un problema XY molto classico in quanto OP pensa di dover testare i suoi metodi privati ​​per un motivo o per l'altro, quando in realtà potrebbe affrontare i test da un punto di vista più pragmatico, visualizzando il privato metodi come semplici funzioni di supporto per quelli pubblici, che sono il contratto con gli utenti finali della classe.
Ampt

3

Ci sono alcune opzioni per farlo, ma tieni presente che (in sostanza) modificano l'interfaccia pubblica dei tuoi moduli, per darti accesso ai dettagli di implementazione interna (trasformando efficacemente i test unitari in dipendenze client strettamente accoppiate, dove dovresti avere nessuna dipendenza).

  • potresti aggiungere una dichiarazione di amico (classe o funzione) alla classe testata.

  • è possibile aggiungere #define private publicall'inizio dei file di test, prima di #includeinserire il codice testato. Nel caso in cui il codice testato sia una libreria già compilata, ciò potrebbe rendere le intestazioni non più corrispondenti al codice binario già compilato (e causare UB).

  • potresti inserire una macro nella tua classe testata e decidere in seguito cosa significa quella macro (con una definizione diversa per il codice di test). Ciò ti consentirebbe di testare gli interni, ma consentirebbe anche a un codice client di terze parti di entrare nella tua classe (creando la propria definizione nella dichiarazione che aggiungi).


2

Ecco un suggerimento discutibile per una domanda discutibile. Non mi piace l'accoppiamento di un amico perché il codice rilasciato deve sapere del test. La risposta di Nir è un modo per alleviarlo, ma non mi piace ancora molto cambiare classe per adeguarmi al test.

Dal momento che non faccio affidamento sull'ereditarietà spesso, a volte faccio semplicemente proteggere i metodi altrimenti privati ​​e ho una classe di test che eredita ed espone secondo necessità. La realtà è che l'API pubblica e l'API di test possono differire ed essere ancora distinte dall'API privata, che ti lascia in una sorta di legame.

Ecco un esempio pratico del tipo a cui ricorrere a questo trucco. Scrivo codice incorporato e ci affidiamo un po 'alle macchine a stati. L'API esterna non deve necessariamente conoscere lo stato della macchina a stati interna, ma il test dovrebbe (probabilmente) verificare la conformità allo schema della macchina a stati nel documento di progettazione. Potrei esporre un getter di "stato corrente" come protetto e quindi dare accesso al test a quello, permettendomi di testare la macchina a stati in modo più completo. Trovo spesso questo tipo di classe difficile da testare come una scatola nera.


Sebbene si tratti di un approccio Java, questo è abbastanza standard per rendere predefinite le funzioni private anziché consentire ad altre classi nello stesso pacchetto (le classi di test) di poterle vedere.

0

Puoi scrivere il tuo codice con molte soluzioni alternative per impedirti di usare gli amici.

Puoi scrivere classi e non avere mai alcun metodo privato. Tutto quello che devi fare è creare funzioni di implementazione all'interno dell'unità di compilazione, lasciare che la tua classe le chiami e trasmetta tutti i membri di dati a cui devono accedere.

E sì, ciò significa che è possibile modificare le firme o aggiungere nuovi metodi di "implementazione" senza modificare l'intestazione in futuro.

Devi valutare se ne vale la pena. E molto dipenderà davvero da chi vedrà la tua intestazione.

Se sto usando una libreria di terze parti, preferirei non vedere le dichiarazioni degli amici ai loro tester di unità. Non voglio nemmeno costruire la loro biblioteca e fare i loro test quando lo faccio. Sfortunatamente troppe librerie open source di terze parti che ho creato lo fanno.

Il test è compito degli autori della biblioteca, non dei suoi utenti.

Tuttavia, non tutte le classi sono visibili all'utente della tua libreria. Molte classi sono "implementazioni" e le implementate nel modo migliore per garantirne il corretto funzionamento. In quelli, potresti avere ancora metodi e membri privati ​​ma vuoi che i tester di unità li testino. Quindi vai avanti e fallo in questo modo se ciò porterà a un codice robusto più veloce, che è facile da mantenere per coloro che devono farlo.

Se gli utenti della tua classe sono tutti all'interno della tua azienda o squadra, puoi anche rilassarti un po 'di più su quella strategia, supponendo che sia consentito dagli standard di codifica della tua azienda.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.