Copertura del codice e programmazione difensiva (in funzioni private)

0

Supponendo che tu voglia costruire test automatici per la seguente (molto semplice, se dispari) classe.

// Calculates the distance between neighboring values in a vector
// and provides functions to return the distance from one index to the following
// or to find the first occurrence of a specified distance
class NeighborDistanceFinder
{
  public:
    NeighborDistanceFinder(std::vector<int> inputVector)
        : m_neighborVector(inputVector) { };

    int getDistanceToNextIndex(int index) const
    {
        if (index >= 0 && index < m_neighborVector.size() - 1)
            return (calculateDistance(index, index + 1);
        throw "Provided index is invalid";
    }

    int getIndexOfFirstOccurenceOfDistance(int distance) const
    {
        for (int index = 0; index < m_neighborVector.size() - 1; i++)
        {
            if (calculateDistance(index, index + 1) == distance)
                return index;
        }
        throw "Distance not found";
    }

  private:
    std::vector<int> m_neighborVector;

    int calculateDistance(int index, int otherIndex) const
    {
        const int lastIndex = m_neighborVector.size() - 1;
        if (index < 0 || otherIndex < 0 || index > lastIndex  || otherIndex > lastIndex)
            throw "Index of of bounds"; //This should never happen
        return m_neighborVector.at(firstIndex) - m_neighborVector.at(secondIndex);
    }
}

In una classe così semplice il controllo dei limiti per calculateDistance() è probabilmente eccessivo, ma immagina che la condizione per controllare sia più complessa di un semplice controllo dei limiti e più funzioni che chiamano calculateDistance() , funzioni funzioni con m_neighborVector e presto sembra sempre più attraente ...

In ogni caso, con questo capolavoro a portata di mano ci rendiamo conto che non abbiamo fatto il TDD e non abbiamo nessun test unitario. Beh, niente paura, sono abbastanza semplici da aggiungere (... qualche ora dopo ...). Fatto.

Ma c'è una lacuna nella nostra copertura del codice: non possiamo raggiungere throw "Index of of bounds"; (a meno che non abbia commesso un errore nello scrivere questo esempio ...). Bene, naturalmente, prendiamo i casi fuori limite nelle due funzioni pubbliche che lo invocano, e la funzione è privata, quindi non possiamo invocarla direttamente (poiché non abbiamo una riflessione in C ++). Lo so, l'analisi statica del codice dovrebbe essere in grado di dirci che questo è essenzialmente un codice morto, ma ancora: Immagina che la condizione sia più complessa.

Quindi cosa si dovrebbe fare riguardo a questa funzione privata molto difensiva a questo punto?

  1. Il divario va bene - non hai bisogno (e non otterrai mai) copertura al 100% - vai avanti.
  2. Questa è una programmazione difensiva eccessiva in una funzione privata - elimina il controllo, FFS.
  3. Devi coprire questo - spostare la funzione in protetto o rendere il tuo test una classe di amici, ecc.
  4. E ora qualcosa di completamente diverso?
posta CharonX 27.06.2018 - 12:23
fonte

2 risposte

3

Dipende dai tuoi livelli di rischio accettabili.

Per la maggior parte del codice, sarà opportuno mantenere il controllo, ma trasformarlo in un'affermazione. Se il controllo fallisce, indica un difetto profondo nella tua logica; non ha senso usare un'eccezione che potrebbe essere catturata. È possibile annotare la dichiarazione di asserzione per escluderla dalle metriche di copertura del codice. Questo dipende dal tuo strumento, ad es. con // LCOV_EXCL_LINE commenti per lcov o gcovr.

Se un errore sarebbe assolutamente inaccettabile, è necessario verificare che il controllo non funzioni correttamente. In C ++, usare una classe di amici per il test è spesso la soluzione meno pazza.

Un'altra tecnica consiste nel separare il codice in una classe base interna, non codificata, facile da testare e quindi scrivere l'API pubblica come un sottile strato su di esso. Il design generale potrebbe essere simile a:

namespace detail {
  class BaseNeighborDistanceFinder
  {
  public:
    ...
  };
}

class NeighborDistanceFinder
  : private detail::BaseNeighborDistanceFinder
{
  ...
};

O in modo equivalente, l'API pubblica può delegare a un oggetto membro detail::BaseNeighborDistanceFinder privato. Il punto è che mentre i test unitari sono generalmente solo un altro client dell'API pubblica, a volte può essere ragionevole rendere pubbliche diverse parti per l'API effettiva e per i test. Usa il tuo giudizio professionale qui, poiché l'approccio "Rendi tutto pubblico!" Ha degli svantaggi: i tuoi test diventano più fragili e il tuo codice potrebbe diventare meno gestibile accidentalmente iniziando a dipendere da parti pubbliche solo per test.

    
risposta data 27.06.2018 - 12:59
fonte
0

Invito sempre gli sviluppatori a scomporre il codice in funzioni testabili con un solo lavoro.

Esempio:

namespace detail
{
    // this is a pure function - it's 100% testable
    int calculateDistance(std::vector<int> const& neighborVector, int index, int otherIndex)
    {
        const int lastIndex = neighborVector.size() - 1;
        if (index < 0 || otherIndex < 0 || index > lastIndex  || otherIndex > lastIndex)
            throw "Index of of bounds"; //This should never happen
        return neighborVector.at(index) - neighborVector.at(otherIndex);
    }
}


// impure function implemented in terms of pure function.
// now the only doubt is "did I supply the correct arguments?"    
int NeighborDistanceFinder::calculateDistance(int index, int otherIndex) const
{
    return detail::calculateDistance(m_neighborVector, index, otherIndex);
}
    
risposta data 29.06.2018 - 16:34
fonte

Leggi altre domande sui tag