Presentazione di TDD in un prodotto legacy

4

Vorrei introdurre pratiche di sviluppo basate su test in un prodotto legacy stabilito. Come con la maggior parte dei prodotti legacy, potremmo utilizzare il miglioramento della qualità tramite una sicura e graduale gestione del refactoring / codice e TDD sia per nuove funzionalità che per difetti potrebbe fare molto.

Tuttavia, ci sono alcune sfide: in alcuni casi la scrittura di test richiede un grande refactoring in sé (cioè nessuna interfaccia testabile, o dipendenze basate su tempistiche arcane). C'è anche il fattore umano: il TDD è un grande cambiamento rispetto al processo attuale.

Quindi, sto cercando la condivisione dell'esperienza - qualcuno ha avuto una situazione simile con l'introduzione di TDD in un prodotto legacy? Se sì, cosa ha funzionato e cosa no?

    
posta RomanK 24.10.2016 - 02:58
fonte

2 risposte

10

TDD non si applica realmente alla manutenzione. Invece, Test Driven Development è un modello iterativo che si concentra sulla fase sviluppo o implementazione . Normalmente, il ciclo di vita del software produrrebbe inizialmente disegni, che sono implementati come codice, che viene quindi testato e amp; rilasciato. TDD riordina questi per ogni iterazione: scriviamo i test prima , implementiamo abbastanza codice per farli passare, il refactoring il codice per farlo assomigliare ad un design ragionevole.

È ancora possibile utilizzare TDD durante la creazione o la modifica della funzionalità. Questo è particolarmente semplice per il nuovo codice. In che modo quel codice interagisce con il resto dell'applicazione? Possiamo catturare quelle interazioni e trasformarle in cuciture verificabili. Quando ciò non è possibile o ragionevole, a causa dello sforzo richiesto o dell'architettura folle che risulterebbe, non possiamo applicare TDD con i test unitari. Invece, dovremo esprimere i nostri test in termini di componenti più grandi o anche in termini dell'intero sistema. Ogni sistema ha almeno una cucitura: il limite del sistema. Ovviamente tali test diventano incredibilmente ingombranti, ma nulla è completamente non verificabile.

A livello sociale, è difficile far rispettare TDD. TDD è una possibile struttura per un ciclo di produttività personale e non è realmente applicabile per un'intera squadra.

Un ciclo red-green-refactor potrebbe essere rapido come pochi secondi fino a minuti, quindi non è possibile applicare un processo qui. Se gli sviluppatori vogliono allenare le loro abilità personali di TDD, la programmazione della coppia può essere efficace. I due sviluppatori possono aiutarsi l'un l'altro a rimanere in linea e attenersi al ciclo red-green-refactor invece di scrivere codice che ritengono possa essere necessario in futuro.

Oltre a non essere applicabile, TDD potrebbe non essere applicabile a tutti gli sviluppatori. Per esempio. Preferisco scrivere prima il codice, poi testare metodicamente & refactoring fino a coprire tutti i casi limite. Il risultato finale - funzionalità con un'eccellente copertura del test - è indistinguibile dal codice scritto con TDD.

Se l'intero team vuole impegnarsi ad aumentare la copertura del test, l'utilizzo di un server CI può essere prezioso. Dopo ogni controllo, viene eseguita l'intera suite di test. Sarebbe possibile contrassegnare la compilazione come fallita se la copertura del test scende (cioè è stato aggiunto un nuovo codice senza test). Tuttavia, tale metrica può essere facilmente utilizzata e la copertura del test non è una misura della qualità del test.

Più spesso dell'introduzione di pratiche TDD in un sistema legacy, siamo interessati a mettere in discussione un sistema legacy. Come hai avuto esperienza, questo può essere piuttosto difficile quando l'applicazione non è stata progettata per testabilità. Ma si tratta solo di test, non di TDD. Ho anche lottato con questo, ma ho scoperto che è possibile generare le giunture necessarie attraverso trasformazioni semplici e meccaniche che non influiscono sulla correttezza del codice su cui si sta lavorando.

In poche parole: nel codice che stiamo cercando di testare, identificare le dipendenze rilevanti su funzioni o oggetti esterni. Possiamo quindi definire un'interfaccia per questi e consentire ai chiamanti del nostro codice di fornire la propria implementazione per queste interfacce. Tuttavia, forniamo valori predefiniti che inoltrano tutte le chiamate al comportamento corrente. Questo refactoring mantiene il comportamento esatto e mantiene la compatibilità con la fonte: mentre la firma del codice in prova è cambiata, nessun codice di chiamata deve essere aggiornato. Un esempio di Python:

def system_under_test(a, b):
  ...
  result = external_function(b, c)
  ...

Questo può essere tranquillamente refactored in:

def system_under_test(a, b, external_function_service=None):
  if external_function_service is None:
    external_function_service = external_function
  ...
  result = external_function_service(b, c)
  ...

Se la dipendenza esterna non è una funzione ma una classe o un oggetto, l'implementazione predefinita sarebbe un Adapter che collega l'implementazione corrente alla nuova interfaccia che abbiamo introdotto. Ho scritto una recensione più approfondita su questo approccio sul mio blog, sebbene alcune parti siano specifiche per C ++: Estrai le tue dipendenze - Rendere il tuo codice pronto per essere testato .

    
risposta data 24.10.2016 - 14:23
fonte
4

Assegna risorse per scrivere test per il vecchio codice e refactoring dei bit non testabili. Questo sarà un processo molto lento e graduale, ma sarà lento, non importa quello che fai dato che vuoi rendere il codice più stabile, non meno, ecco perché stai adottando TDD in primo luogo e se tu rompere troppe cose contemporaneamente che rallenteranno ulteriormente le cose.

Tieni nota di quali parti del codice tendono ad avere più bug o cambiano più frequentemente con nuove funzionalità e inizia solo con quelle. Ecco dove vedrai il massimo impatto aggiungendo test e refactoring. Quel modulo criptico che nessuno capisce veramente, ma che non causa molti problemi e quasi mai cambiamenti può rimanere fermo per un po '.

Questo sarà un processo auto-rinforzante, dal momento che più test aggiungi, più facile sarà il refactoring e viceversa.

Imposta i target per ogni iterazione e monitora i progressi. Vuoi avere un codice meno testato ogni settimana, non deve essere molto meno, solo meno. Hai ancora del lavoro da fare sul progetto, dopotutto.

Avere una squadra pratica dove nessun nuovo codice passerà la revisione del codice senza test adeguati. Cogli l'opportunità di includere strumenti di analisi statica come i linters nel tuo processo e attenersi a una guida di stile rigorosa per il nuovo codice.

    
risposta data 24.10.2016 - 12:20
fonte

Leggi altre domande sui tag