2011-12-19 10 views
6

Il codice si evolve, e come fa, decade anche se non potato, un po 'come un giardino in questo senso. La potatura significa refactoring per farlo soddisfare il suo scopo in evoluzione.Come faccio a essere sicuro di non rompere il codice di prova quando lo rifatto?

Il refactoring è molto più sicuro se abbiamo una buona copertura di prova unitaria. Lo sviluppo basato su test ci obbliga a scrivere prima il codice di test, prima del codice di produzione. Quindi, non possiamo testare l'implementazione, perché non ce n'è. Questo rende molto più facile il refactoring del codice di produzione.

Il ciclo TDD è simile al seguente: scrittura di un test, test fallito, scrittura del codice di produzione fino al completamento del test, refactoring del codice.

Ma da quello che ho visto, le persone refactoring il codice di produzione, ma non il codice di prova. Con il decadimento del codice di prova, il codice di produzione diventerà obsoleto e poi tutto andrà giù. Pertanto, penso che sia necessario refactoring codice di test.

Ecco il problema: come assicurarsi di non rompere il codice di prova quando lo si rifatta?

(ho fatto un approccio, https://thecomsci.wordpress.com/2011/12/19/double-dabble/, ma penso che ci potrebbe essere un modo migliore.)

A quanto pare c'è un libro, http://www.amazon.com/dp/0131495054, che non ho ancora letto.

C'è anche una pagina Wiki su questo, http://c2.com/cgi/wiki?RefactoringTestCode, che non ha una soluzione.

+3

Perché si esegue il refactoring del codice di test? Che cosa si ottiene da questo? Non penso di seguire la domanda. Puoi spiegare perché è così importante rifattorizzare il test? Il codice non marcisce spontaneamente. La decomposizione del codice si riferisce a cambiamenti mal pianificati. Quali cambiamenti mal pianificati si verificano nel codice di test? Puoi fornire un esempio concreto? –

+1

Scrivo il test, poi scrivo il codice di produzione, quindi rifatto il codice, sia test che produzione. Potrei anche refactoring il codice di prova più tardi, quando refactoring altro codice di test, al fine di ridurre la ripetizione per esempio. –

+1

"per ridurre la ripetizione"? Perché? Non sono ancora chiaro perché avresti mai toccato il codice di prova. –

risposta

1

Il refactoring dei test è un processo in due fasi. Detto semplicemente: per prima cosa devi usare la tua applicazione sotto test per assicurarti che i test passino durante il refactoring. Quindi, dopo che i test refactored sono verdi, devi assicurarti che falliscano. Tuttavia per farlo correttamente richiede alcuni passaggi specifici.

Per testare correttamente i test refactored, è necessario modificare l'applicazione sottoposta a test per provocare il fallimento del test. Solo quella condizione di test dovrebbe fallire. In questo modo puoi assicurarti che il test fallisca correttamente oltre al passaggio. Dovresti sforzarti per un singolo test fallito, ma ciò non sarà possibile in alcuni casi (ad esempio non in unit test). Tuttavia, se si esegue correttamente il refactoring, si verificherà un singolo errore nei test refactored e gli altri errori saranno presenti nei test non correlati al refactoring corrente. La comprensione della base di codice è necessaria per identificare correttamente i fallimenti a cascata di questo tipo e gli errori di questo tipo si applicano solo a test diversi dai test di unità.

+0

Quindi: 1. Modificare il codice di produzione in modo che il test diventi rosso. 2. Sostituirlo. 3. Riforma il codice di test, assicurandosi che diventi ancora verde. 4. Modificare nuovamente il codice di produzione, allo stesso modo. 5. Se il test diventa rosso, il refactor è OK. Spero non ti dispiaccia se cambio la tua risposta per essere più preciso. :-) –

+0

Mi piace l'idea che solo un test dovrebbe diventare rosso quando "interrompo" il codice di produzione. –

1

Um.

PER SOLUZIONE JAVA! Non so in che lingua stai programmando!

Ok, ho appena letto "Clean Code" di uno dei Martins, un libro che sostiene che l'idea di refactoring del codice di test per mantenere pulito e leggibile è una buona idea, e in effetti un obiettivo. Quindi l'ambizione di rifattorizzare e mantenere il codice pulito è buona, non un'idea sciocca come pensavo prima.

Ma non è quello che hai chiesto, quindi proviamo a rispondere!

Mi piacerebbe tenere un db dei vostri test - o l'ultimo risultato del test, comunque. Con un po 'di java annotazione, si può fare qualcosa di simile:

@SuperTestingFramerworkCapable 
public class MyFancyTest { 

    @TestEntry 
    @Test 
    public testXEqualsYAfterConstructors(){ 
     @TestElement 
     //create my object X 

     @TestElement 
     //create my object Y 

     @TheTest 
     AssertTrue(X.equals(Y)); 
    } 
} 

In ogni caso, si sarebbe anche bisogno di una riflessione e di super classe annotazioni di elaborazione, che avrebbe ispezionare questo codice. Potrebbe essere solo un ulteriore passo in avanti nell'elaborazione: scrivere test, passare attraverso questo super processore e poi, se dovesse superarlo, eseguire i test. E il processore super-sta per utilizzare uno schema MyFancyTest

E per ogni membro che hai nella tua classe, userà una nuova tabella - qui il (solo) tabella sarebbe testXEqualsYAfterConstructors E quel tavolo avrebbe colonne per ogni elemento contrassegnato con l'annotazione @TestElement. E sarebbe anche una colonna per @TheTest suppongo che tu chiameresti solo le colonne TestElement1, TestElement2 ecc. Ecc.

E POI, una volta impostato tutto, salverebbe solo i nomi delle variabili e la riga annotato @ TheTest. Quindi la tabella sarebbe

testXEqualsYAfterConstructors 
TestElement1  | TestElement2  | TheTest 
SomeObjectType X | SomeObjectType X | AssertTrue(X.equals(Y)); 

Quindi, se il processore super-va e trova esistono tabelle, allora si può confrontare ciò che è già lì con quello che oggi è nel codice, e può generare un avviso per ogni diversa iscrizione. E puoi creare un nuovo utente - un amministratore - che può ottenere le modifiche e controllarle, crogiolo e ok o no.

E poi si può commercializzare questa soluzione per questo problema, si vende compagnia per 100M e mi danno il 20%

applausi!

giorno lento, ecco il razionale: TUO soluzione utilizza un sacco di overhead in più, più pericolosamente, nel codice di produzione vera e propria. Il tuo codice prodotto non dovrebbe essere legato al tuo codice di test, mai, e certamente non dovrebbe avere variabili casuali che sono specifiche del test in esso. Il prossimo suggerimento che ho con il codice che hai inserito è che il tuo framework non impedisce alle persone di rompere i test. Dopo tutto, si può avere questo:

@Test 
public void equalsIfSameObject() 
{ 
    Person expected = createPerson(); 
    Person actual = expected; 

    check(Person.FEATURE_EQUAL_IF_SAME_OBJECT); 
    boolean isEqual = actual.equals(expected); 

    assertThat(isEqual).isTrue(); 
} 

Ma se cambio le ultime due righe di codice in qualche "refactoring" di classi di test, allora il vostro quadro sta per segnalare un successo, ma il test non sarà Fai qualcosa. È davvero necessario assicurarsi che venga generato un avviso e che le persone possano guardare alla "differenza".

Quindi, di nuovo, si potrebbe semplicemente voler usare svn o perforce e crogiolo per confrontare e controllare questa roba!

Inoltre, visto che sei appassionato di una nuova idea, ti consigliamo di leggere le annotazioni locali: http: //stackoverflow.com/questions/3285652/how-can-i-create-an-annotation -processor-that-processes-a-local-variabile

Um, quindi potrebbe essere necessario ottenere quello di lui - vedere l'ultimo commento nel link sopra - potrebbe essere necessario anche il suo compilatore java personalizzato.

@Disclaimer Se si crea una nuova società con il codice che segue più o meno quanto sopra, mi riservo il diritto al 20% della società, se e quando si è un valore di oltre 30M, in un momento della mia scelta

+0

Se modificherei il tuo ultimo esempio in un test che non testasse nulla, il framework mu verrebbe effettivamente lagnato, perché poi non fallirebbe quando lo ricontromettere disabilitando il codice di produzione. –

+0

mmm, ok, modifica l'ultimo esempio in modo che ritorni solo se il codice di produzione è attivo o meno. – bharal

2

Penso che non dovresti cambiare il tuo codice di test.

Perché? In TDD, si definisce un'interfaccia per una classe. Questa interfaccia contiene metodi definiti con un determinato insieme di funzionalità. I ​​requisiti /design.

Primo: Questi requisiti non cambiano durante il refactoring del codice di produzione. Refactoring significa: cambiare/pulire il codice senza cambiare la funzionalità.
Secondo: Il test verifica un determinato insieme di funzionalità, questo set rimane invariato.

Conclusione: Test di refactoring e refactoring del codice di produzione sono due cose diverse.

Suggerimento: quando si scrivono i test, scrivere codice pulito. Fai piccoli test. Che prova davvero un pezzo della funzionalità.

Ma "Il tuo progetto cambia a causa di cambiamenti imprevisti ai requisiti". Questo lead o non può portare a modifiche nell'interfaccia.
Quando i requisiti cambiano, i test devono cambiare. Questo non è evitabile.
È necessario tenere presente che questo è un nuovo ciclo TDD. Prima prova la nuova funzionalità e rimuovi i vecchi test di funzionalità. Quindi attuare il nuovo design.

Per fare in modo che funzioni correttamente, sono necessari test puliti e di piccole dimensioni. Esempio:

MethodOne does: changeA and changeB 

Don't put this in 1 unit test, but make a test class with 2 unit tests. 
Both execute MethodOne, but they check for other results (changeA, changeB). 

When the specification of changeA changes, you only need to rewrite 1 unit method. 
When MethodOne gets a new specification changeC: Add a unit test. 

Con l'esempio sopra i test sarà più agile e più facile da cambiare.

Sommario:

  • Non refactoring del test, quando refactoring del codice di produzione.
  • Scrivi test puliti e agili.

Si spera che questo aiuti. Buona fortuna.

@disclaimer: Non voglio i tuoi soldi se questo ti rende ricco.

+2

Ottimo ma questo manca il punto IMO. La questione non riguardava la modifica del codice di test a seguito della modifica dei requisiti. Riguardava * il refactoring * del codice di test. "Non effettuare il refactoring dei test, quando si esegue il refactoring del codice di produzione." => questo principio è implicito in TDD. Riesci sempre a ripetere i test tra 2 azioni di refactoring, facendo un passo alla volta. – guillaume31

+0

Capisco la tua dichiarazione. Mi riferisco al fatto che devi scrivere un test pulito, in questo modo non hai bisogno di rifattorizzare i test. Se ritieni che ciò non sia possibile, dovresti seguire il principio per testare le specifiche dei tuoi metodi in base alle specifiche. In questo modo puoi creare test il più possibile piccoli e pertinenti. Ok e qualche volta devi refactoring i tuoi test. C'è solo una spiegazione: non si è mai sicuri del refactoring a meno che non si sia verificato l'implementazione. Ciò si è tradotto nella domanda: puoi farlo solo testando i tuoi test (che è eccessivo). –

+1

@MatsStijlaart Ho allenato un paio di team che hanno appena iniziato con i test unitari. Non scrivono lì test pulito dall'inizio. Ho insegnato loro come utilizzare modelli come Builder per ripulire i test, per dividere i test per testare solo una cosa e altre cose per assicurarsi che il team non smetta di scrivere test perché è difficile usarli. –

1

Come si fa a garantire che non si interrompa il codice di prova quando si refactor esso?

Il riesame dei test dovrebbe essere sufficiente nella maggior parte dei casi.

Ci sono altre strategie descritte here ma potrebbero essere eccessive rispetto ai pochi vantaggi che si ottengono.

+0

+1 per il collegamento. –

1

Circa due mesi prima che la domanda fosse una delle mie principali domande nel refactoring.Basta lasciatemi spiegare la mia esperienza:

  • quando si vuole il refactoring di un metodo, si dovrebbe coprire con test di unità (o qualsiasi altro test) per essere sicuri che non si sta rompendo qualcosa durante il refactoring (nel mio caso la il team sapeva che il codice funzionava bene perché lo utilizzavano da 6 anni, avevano solo bisogno di migliorarlo, quindi tutti i miei test unitari sono stati superati nel primo passaggio. Quindi, nel primo passaggio, sono stati superati alcuni test unitari che coprono interi scenari. Se alcuni test unitari falliscono, in primo luogo è necessario risolvere il problema per assicurarsi che il metodo funzioni correttamente.

  • dopo aver superato tutti i test, è stato effettuato il refactoring del metodo e si desidera eseguire il test per assicurarsi che tutto sia corretto. Qualche modifica nei codici di prova?

  • è necessario scrivere test indipendenti dalla struttura interna del metodo. Dopo il refactoring, dovresti solo modificare una piccola parte del codice e nella maggior parte dei casi non sono richieste modifiche, perché il refactoring migliora solo la struttura e non modifica il comportamento. Se il tuo codice di test doveva cambiare molto, non sai mai se hai rotto alcune cose sul codice principale durante il refactoring o meno.

  • cosa e più importante per me è quello di ricordare in ogni test, un comportamento dovrebbe essere considerato

Spero di poter spiegare bene.

Problemi correlati