È il refactoring allo scopo di testare un odore di codice?

4

Consentitemi di prefigurare questa domanda dicendo che ho bisogno di test delle unità. L'ho dolorosamente capito. Devi farlo per garantire che le future modifiche non influiscano negativamente sull'applicazione in modi che non ti aspetti. È inoltre buona norma utilizzare una metodologia basata sui test per garantire che le modifiche correnti funzionino come previsto, fornendo al contempo una solida base di test per l'aggiunta di casi limite (bug) in un secondo momento. Capisco anche che il test è un requisito per qualsiasi azienda che intende o è soggetta a un audit IT. Infine, se il tuo codice è testabile, è probabile che sia stato scritto utilizzando solidi principi di progettazione.

Il dilemma che ho è quello di codificare un'interfaccia allo scopo di sostenere da solo il test delle unità, sembra una complessità forzata.

Prendete il caso in cui avete un'applicazione di database che non ha il requisito di essere indipendente dal database. Hai già codificato un DAL che comunica con quel database e BL che usa quel DAL per la persistenza. Sono in atto test di integrazione con l'architettura corrente che utilizza un database di sviluppo per i dati di test.

Per supportare la copertura del codice al 100% sui test unitari ora è necessario introdurre interfacce sia per il DAL che per il BL, avere implementazioni concrete di tali interfacce e utilizzare la dipendenza o l'iniezione del costruttore per utilizzare tali interfacce in base al contesto del tempo di esecuzione.

Questo non introduce una complessità forzata? Una domanda secondaria, immagino, dov'è la linea tracciata tra unità e test di integrazione in questi casi? È certamente possibile che il modello di dati possa cambiare e che si desideri test di integrazione in aggiunta al test delle unità, poiché è chiaro che i test delle unità avranno esito positivo laddove il test di integrazione può fallire in quel caso.

    
posta Mufaka 28.02.2014 - 02:32
fonte

4 risposte

7
  1. La copertura del 100% del codice è un obiettivo poco pratico - diciamo i fatti qui, per ottenere quella copertura, alcuni test saranno il tipo di cover il codice ma non test esso. Peggio ancora, stai impiegando molto tempo per eseguire test che non causano errori o che hanno un impatto minimo se si verificano errori.

  2. Tutte le applicazioni di database richiedono un comportamento agnostico del database - oh se avessi un dollaro ogni volta che qualcuno diceva "oh, questo non cambierà". Certo, il tuo database potrebbe non cambiare, ma che ne è della licenza di quel codice a terze parti? Che ne dici di metterlo su una piattaforma mobile? Che dire di metterlo dietro un servizio web? Che dire di tutte le cose strane e interessanti che faremo in 5-10 anni che non puoi immaginare?

  3. (IMO) Non cambiare il tuo design solo per testabilità - e ora il punto della tua domanda e il punto più controverso che farò: hai assolutamente ragione quel codice verificabile è probabilmente un codice ben progettato. Se hai intenzione di rifattorizzare il tuo codice, non farlo per rendere il codice più testabile, rendilo meglio disegnato . I test unitari fungono da grande proxy per esigenze alternative. Se è difficile da testare, sarà probabilmente difficile riutilizzarlo. Ma a volte fai un compromesso sul fatto che sarà difficile da usare in cambio di prestazioni, o una bella interfaccia, o per semplificare il codice ... Non peggiorare il tuo codice di produzione in favore del tuo codice di prova I test sono lì per servire tu .

Ma sii prudente: il caso comune è che il codice di test difficile da eseguire è difficile da riutilizzare, e un refactoring ben fatto aiuterà il tuo codice di test e il tuo codice di produzione.

    
risposta data 28.02.2014 - 04:29
fonte
5

Alcune buone risposte qui, ma vorrei aggiungere qualcosa per rispondere a questa parte della tua domanda:

To support 100% code coverage on unit tests you would now need to introduce interfaces for both the DAL and the BL, have concrete implementations of those interfaces, and use dependency or constructor injection to utilize those interfaces based on the context of the runtime.

Dipende dal modo in cui hai progettato il tuo DAL e il tuo BL. Un DAL ben progettato dovrebbe consentire di creare oggetti completamente senza un database , in memoria. E il tuo BL non dovrebbe fare affidamento su come ottenere i suoi oggetti DAL da un database - il BL dovrebbe usare un approccio come il schema del repository per recuperare qualsiasi oggetto DAL. Ciò consentirà un facile test automatico per il DAL e il BL senza aggiungere interfacce per ogni singolo oggetto DAL - saranno necessarie solo interfacce per i repository (e mock di repository per i test).

Potresti sostenere che in questo modo non produci test unità reali in questo modo per il BL, perché non puoi testarli separatamente dai tuoi oggetti DAL, ma finché gli oggetti DAL sono non molto più di contenitori di dati, questo è IMHO perfettamente accettabile e un compromesso pragmatico.

Does that not introduce a contrived complexity?

Il tuo sistema conterrà più pezzi, quindi sarà più complesso, ma ogni pezzo diventerà meno complicato - il che aumenterà l'evolvibilità generale del tuo sistema.

    
risposta data 28.02.2014 - 05:46
fonte
2

Dovresti avere dei test unitari per il tuo codice DAL e BL. Le due parti del codice dovrebbero funzionare indipendentemente l'una dall'altra e non dipendere dai dettagli di implementazione dell'altra. Writing Unit Tests non solo verifica il tuo codice, ma è anche uno strumento per garantire che ci sia quella separazione delle preoccupazioni.

Potrebbe sembrare una grande quantità di lavoro e sovraccarico, ma immagina 6 mesi lungo la strada che il database non è in scala come speravi. Decidi di passare a un archivio di documenti con valore chiave come reddis. Finché l'implementazione di Reddis implementa completamente l'interfaccia DAL e supera tutti i test di unità che hai scritto per questo, puoi essere estremamente sicuro che il passaggio a quelle classi dovrebbe essere senza intoppi (ignorando ovviamente i problemi di migrazione dei dati).

Anche se sembra difficile ora, una volta che hai imparato a scrivere codice per i test unitari, lo troverai meno di un lavoro di routine e più di uno strumento per scrivere codice migliore. Troverete che i test di integrazione tendono a essere fragili e li scriverete sempre meno.

Direi che il bisogno di refactoring del codice per supportare i test delle unità è un odore di codice, motivo per cui li scriviamo.

    
risposta data 28.02.2014 - 04:07
fonte
0

Sì, c'è un catch-22 coinvolto in alcuni test di unità: per essere in grado di testare il codice a volte devi cambiarlo. Questo a volte è inevitabile.

Come regola generale, una volta passato a Data Access, non è più un test unitario. Pensa a un test unitario come qualcosa che esercita puramente la tua logica e le trasformazioni interne dei dati, senza fare IO.

Il che ci riporta alla necessità di falsificare l'accesso ai dati con interfacce e false implementazioni.

    
risposta data 28.02.2014 - 03:50
fonte

Leggi altre domande sui tag