Gli spazi dei nomi anonimi rendono il codice non testabile

11

Ecco un tipico codice C ++:

foo.hpp

#pragma once

class Foo {
public:
  void f();
  void g();
  ...
};

foo.cpp

#include "foo.hpp"

namespace {
    const int kUpperX = 111;
    const int kAlternativeX = 222;

    bool match(int x) {
      return x < kUpperX || x == kAlternativeX;
    }
} // namespace

void Foo::f() {
  ...
  if (match(x)) return;
  ...

Sembra un discreto codice C ++ idiomatico - una classe, una funzione helper match che viene utilizzata dai metodi di Foo , alcune costanti per quella funzione di supporto.

E poi voglio scrivere dei test.
Sarebbe perfettamente logico scrivere un test unitario separato per match , perché non è affatto banale.
Ma risiede in un namespace anonimo.
Ovviamente posso scrivere un test che chiamerebbe Foo::f() . Tuttavia non sarà un buon test se Foo è pesante e complicato, tale test non isolerà il soggetto da altri fattori non correlati.

Quindi devo spostare match e tutto il resto fuori dallo spazio dei nomi anonimo.

Domanda: che senso ha inserire funzioni e costanti nello spazio dei nomi anonimo, se le rende inutilizzabili nei test?

    
posta Abyx 12.01.2016 - 18:04
fonte

3 risposte

5

La funzione nell'esempio sembra abbastanza complessa e potrebbe essere meglio spostarla nell'intestazione, allo scopo di testare le unità.

what's the point of putting functions and constants into the anonymous namespace, if it makes them unusable in tests?

Per renderli isolati dal resto del mondo. E non sono solo le funzioni e le costanti che puoi inserire nello spazio dei nomi anonimo, ma anche i tipi.

Tuttavia, se rende le tue unità test molto complesse, allora stai sbagliando. In tal caso la funzione non appartiene a questo. Quindi è il momento per un piccolo refactoring per semplificare i test.

Quindi, nello spazio dei nomi anonimo dovrebbe passare solo funzioni molto semplici, a volte costanti e tipi (inclusi typedef) utilizzati in quell'unità di traduzione.

    
risposta data 12.01.2016 - 18:42
fonte
4

Se vuoi testare unitamente i dettagli dell'implementazione privata, fai lo stesso tipo di schivata per gli spazi dei nomi senza nome come per i membri della classe privati (o protetti):

Interagisci e festeggia.

Mentre per le classi si abusa di friend , per gli spazi dei nomi senza nome si abusa di #include -mechanism, che non ti obbliga nemmeno a cambiare il codice.
Ora che il tuo codice di prova (o meglio solo qualcosa per esporre tutto) è nella stessa TU, non c'è alcun problema.

Una parola di cautela: se verifichi i dettagli dell'implementazione, il test si interromperà se tali cambiamenti. Assicurati di testare solo quei dettagli di implementazione che potrebbero comunque fuoriuscire o accettare che il tuo test sia insolitamente effimero.

    
risposta data 13.01.2016 - 18:53
fonte
3

It would be perfectly logical to write a separate unit test for match, because it's quite non-trivial.

Il codice che hai mostrato per match è un banale 1-liner piuttosto semplice, senza casi complicati, o è come un esempio semplificato? Ad ogni modo, presumo sia semplificato ...

Question: what's the point of putting functions and constants into the anonymous namespace, if it makes them unusable in tests?

Questa domanda è ciò che voleva farmi saltare qui poiché Deduplicator mostrava già un ottimo modo per entrare e ottenere l'accesso attraverso #include trucco. Ma la formulazione qui fa sembrare che testare ogni singolo dettaglio di implementazione interna di tutto sia una sorta di obiettivo finale universale, quando è lontano da esso.

L'obiettivo del testing di unità pari non è sempre quello di testare ogni piccola microscopia interna di funzionalità. La stessa domanda si applica alle funzioni statiche del file scope in C. Puoi anche rendere più difficile rispondere alla domanda chiedendo perché gli sviluppatori usano pimpls in C ++ che richiederebbe entrambi friendship e #include Inganno alla scatola bianca, scambiando facilmente testabilità dei dettagli di implementazione per tempi di compilazione migliorati, ad esempio

Da una prospettiva pragmatica, potrebbe sembrare lordo ma match potrebbe non essere correttamente implementato con alcuni casi limite che lo fanno inciampare. Tuttavia, se la sola classe esterna, Foo , che ha accesso a match non può usarla in un modo che incontra quei casi limite, allora è piuttosto irrilevante per la correttezza di Foo che match ha questi margini casi che non saranno mai riscontrati a meno che Foo cambi, a quel punto i test di Foo falliranno e lo sapremo immediatamente.

Una mentalità più ossessiva desiderosa di testare ogni singolo dettaglio di implementazione interna (forse un software mission-critical, ad esempio) potrebbe voler irrompere e festeggiare, ma molte persone non pensano necessariamente che sia la migliore idea, dato che creerebbe le prove più fragili che si possano immaginare. YMMV. Ma volevo solo indirizzare la formulazione di questa domanda che suona come questo tipo di testabilità a livello di dettaglio interno-a grana fine dovrebbe essere un obiettivo finale, quando anche la mentalità di testing unitaria più rigorosa potrebbe rilassarsi un po 'qui ed evitare di radiografare gli interni di ogni classe.

Quindi perché le persone definiscono le funzioni in spazi dei nomi anonimi in C ++ o come funzioni statiche di scope del file con il collegamento interno in C, nascosto al mondo esterno? E questo è principalmente questo: nasconderli al mondo esterno. Ciò ha una serie di effetti dalla riduzione dei tempi di compilazione alla riduzione della complessità (ciò che non può essere consultato altrove non può causare problemi altrove) e così via. Probabilmente la testabilità dei dettagli di implementazione privata / interna non è la cosa numero uno nelle menti delle persone quando lo fanno, ad esempio, riducendo i tempi di costruzione e nascondendo la complessità non necessaria dal mondo esterno.

    
risposta data 15.01.2016 - 02:59
fonte

Leggi altre domande sui tag