TDD: come gestire la sequenza di avvio ricorrente?

1

Sono nuovo nell'adozione di un approccio completamente TDD che utilizza la DI così da poter prendere in giro ogni dipendenza. Uno dei punti critici che ho identificato finora è il fatto che ogni volta che lavoro nel mio costruttore, ogni test deve prepararsi a quella sequenza (aspettarsi le corrispondenti chiamate simulate). Un esempio di tale sequenza potrebbe essere la creazione di un messaggio di invio o il controllo dell'esistenza di una cartella, ecc.

Esistono buone pratiche su come gestire tali situazioni. Giusto, considero le due opzioni:

  1. Nessun lavoro nel costruttore (ingombrante dal momento che ho bisogno di manualmente per assicurarmi che gli invarianti dell'oggetto siano soddisfatti)
  2. Verifica individualmente la sequenza di avvio e lavora solo con oggetti intializzati nel test

Non sono riuscito a trovare approfondimenti su google come questi problemi vengono gestiti in generale. Sto seguendo un principio di progettazione scadente?

Modifica, Esempio aggiunto:

struct CachedObservable {
    INJECT(CachedObserable(
        std::shared_ptr<IO> io,
        std::shared_ptr<Observable> base
    ))
    {
         load_and_emit_cached_file_from_hdd_if_available(io);
         base->subscribe([this](auto& x) {
             save_value_in_file(cached_filepath);
             emit(x);
         });
    }

    void load_and_emit_cached_file_from_hdd_if_available(
         std::shared_ptr<IO> io
    )
    {
         if (io->file_exists(cached_filepath))
             emit(io->load(cached_filepath));
    }
    ...
};

L'esempio è un'astrazione ma cattura il caso d'uso reale. Io uso frutta e amp; rxcpp per avere un oggetto di riproduzione che faccia due cose:

  1. Durante la costruzione carica un valore memorizzato nella cache da un file sull'HDD ed emette il contenuto
  2. Inoltra tutti i valori emessi dall'osservabile inserito e memorizza nella cache l'ultimo oggetto

Uso i mock sia per io che osservabili per isolare completamente il comportamento della classe

    
posta user823255 02.10.2017 - 04:08
fonte

2 risposte

4

No work in the constructor (cumbersome since I need to manually need to make sure the object's invariants are satisifed)

Nessun lavoro nel costruttore è una buona idea veramente . Flaw: Constructor Real Work , di Miško Hevery, copre questo terreno abbastanza bene. Ha scritto una serie di post su testability . La tecnica principale è spesso chiamata injection dependance ; un collaboratore di oggetti viene passato al costruttore, anziché creato dal costruttore.

Applicare l'invariante invariante dell'oggetto è ancora gestito dal costruttore; la logica del costruttore controlla che i collaboratori richiesti non siano nulli, che i valori siano limitati a un intervallo valido e così via. Potrebbe anche richiedere di verificare che i collaboratori siano soddisfacenti, cosa che si farebbe verificando il collaboratore utilizzando la propria interfaccia pubblica.

Ciò significa che qualsiasi test che crea un oggetto deve fornire un insieme completo di collaboratori; quando questo è macchinoso, potrebbe essere un suggerimento che il tuo oggetto ha troppe responsabilità diverse e dovrebbe essere refactored.

In definitiva, tuttavia, qualsiasi test unitario che coinvolga molti collaboratori richiederà una buona dose di configurazione. Non c'è magia. A volte puoi elidere un numero di collaboratori che non sono particolarmente interessanti per un test specifico sostituendo un collaboratore root con un test double , eliminando la parte del grafico delle dipendenze che normalmente andrebbe lì.

    
risposta data 02.10.2017 - 13:19
fonte
3

È normale dover eseguire operazioni prima del test, e non è molto pratico scrivere test che testano tutto eseguito in un determinato test. L'approccio tipico qui è quello di scrivere un test esplicito, ad esempio test A, per verificare che l'installazione sia stata eseguita correttamente e trattarla come test proprio. Quindi qualsiasi test che richiede la stessa configurazione, test B, presuppone semplicemente che l'installazione sia eseguita correttamente.

L'idea è che se esegui tutti i test, mentre forse il tuo test B ti dà un falso positivo a causa del fatto che il setup è stato fatto correttamente, il tuo test A fallisce, perché stava testando proprio questo.

La chiave qui è che tutto è soggetto a essere testato, anche se non necessariamente nel test che lo richiede.

E no, fai del tuo meglio per evitare di eseguire operazioni "rischiose" nel tuo costruttore. Se necessario, eseguire un carico lento che inizializza l'istanza la prima volta che viene utilizzata. Oppure trattalo come una risorsa che deve essere aperta / chiusa e quindi dovrebbe essere chiamata esplicitamente.

    
risposta data 02.10.2017 - 11:03
fonte

Leggi altre domande sui tag