Domanda

Ultimamente ho dovuto modificare del codice su sistemi più vecchi in cui non tutto il codice dispone di test unitari.
Prima di apportare le modifiche, vorrei scrivere dei test, ma ogni classe ha creato molte dipendenze e altri anti-pattern che hanno reso i test piuttosto difficili.
Ovviamente ho voluto rifattorizzare il codice per renderlo più semplice da testare, scrivere i test e poi modificarlo.
È questo il modo in cui lo faresti?Oppure passeresti molto tempo a scrivere i test difficili da scrivere che verrebbero per lo più rimossi una volta completato il refactoring?

È stato utile?

Soluzione

Prima di tutto, ecco un ottimo articolo con suggerimenti sui test unitari.In secondo luogo, ho scoperto che un ottimo modo per evitare di apportare tonnellate di modifiche al vecchio codice è semplicemente rifattorizzarlo un po' finché non è possibile testarlo.Un modo semplice per eseguire questa operazione è rendere protetti i membri privati ​​e quindi sovrascrivere il campo protetto.

Ad esempio, supponiamo che tu abbia una classe che carica alcune cose dal database durante il costruttore.In questo caso, non puoi semplicemente sovrascrivere un metodo protetto, ma puoi estrarre la logica del DB in un campo protetto e quindi sovrascriverla nel test.

public class MyClass {
    public MyClass() {
        // undesirable DB logic
    }
}

diventa

public class MyClass {
    public MyClass() {
        loadFromDB();
    }

    protected void loadFromDB() {
        // undesirable DB logic
    }
}

e quindi il tuo test assomiglia a questo:

public class MyClassTest {
    public void testSomething() {
        MyClass myClass = new MyClassWrapper();
        // test it
    }

    private static class MyClassWrapper extends MyClass {
        @Override
        protected void loadFromDB() {
            // some mock logic
        }
    }
}

Questo è un pessimo esempio, perché in questo caso potresti usare DBUnit, ma in realtà l'ho fatto di recente in un caso simile perché volevo testare alcune funzionalità totalmente estranee ai dati caricati, quindi è stato molto efficace.Ho anche trovato utile tale esposizione dei membri in altri casi simili in cui ho bisogno di sbarazzarmi di alcune dipendenze che sono presenti in una classe da molto tempo.

Tuttavia, consiglierei questa soluzione se stai scrivendo un framework, a meno che non ti dispiaccia davvero esporre i membri agli utenti del tuo framework.

È un po' un trucchetto, ma l'ho trovato piuttosto utile.

Altri suggerimenti

@valters

Non sono d'accordo con la tua affermazione secondo cui i test non dovrebbero interrompere la build.I test dovrebbero indicare che nell'applicazione non sono stati introdotti nuovi bug per la funzionalità testata (e un bug trovato è un'indicazione di un test mancante).

Se i test non interrompono la build, puoi facilmente imbatterti nella situazione in cui il nuovo codice interrompe la build e non è noto per un po', anche se un test lo ha coperto.Un test fallito dovrebbe essere un segnale di allarme che indica che il test o il codice devono essere corretti.

Inoltre, consentire ai test di non interrompere la build farà sì che il tasso di fallimento aumenti lentamente, fino al punto in cui non si dispone più di un insieme affidabile di test di regressione.

Se si verifica un problema con i test che si interrompono troppo spesso, potrebbe indicare che i test sono stati scritti in modo troppo fragile (dipendenza da risorse che potrebbero cambiare, come il database senza utilizzare correttamente DB Unit o un servizio web esterno questo dovrebbe essere deriso), o potrebbe essere un'indicazione che ci sono sviluppatori nel team che non prestano la dovuta attenzione ai test.

Sono fermamente convinto che un test fallito dovrebbe essere risolto il prima possibile, proprio come si correggerebbe il codice che non riesce a compilare il prima possibile.

Non sono sicuro del motivo per cui diresti che i test unitari verranno rimossi una volta completato il refactoring.In realtà la tua suite di test unitari dovrebbe essere eseguita dopo la build principale (puoi creare una build "test" separata, che esegue semplicemente i test unitari dopo la creazione del prodotto principale).Quindi vedrai immediatamente se le modifiche in un pezzo interrompono i test in un altro sottosistema.Tieni presente che è un po' diverso dall'esecuzione dei test durante la compilazione (come alcuni potrebbero sostenere): alcuni test limitati sono utili durante la compilazione, ma di solito è improduttivo "bloccare" la build solo perché alcuni test unitari falliscono.

Se stai scrivendo Java (è probabile), controlla http://www.easymock.org/ - può essere utile per ridurre l'accoppiamento ai fini del test.

Ho letto Lavorare in modo efficace con il codice legacy e sono d'accordo che sia molto utile per gestire il codice "non testabile".

Alcune tecniche si applicano solo ai linguaggi compilati (sto lavorando su "vecchie" app PHP), ma direi che la maggior parte del libro è applicabile a qualsiasi linguaggio.

I libri sul refactoring a volte presuppongono che il codice sia in uno stato semi-ideale o "consapevole della manutenzione" prima del refactoring, ma i sistemi su cui lavoro sono tutt'altro che ideali e sono stati sviluppati come app "impara man mano che procedi" o come prime app per alcune tecnologie utilizzate (e non biasimo gli sviluppatori iniziali per questo, dato che sono uno di loro), quindi non ci sono test e il codice a volte è confuso.Questo libro affronta questo tipo di situazione, mentre altri libri sul refactoring di solito non lo fanno (beh, non in questa misura).

Dovrei dire che non ho ricevuto denaro dall'editore né dall'autore di questo libro ;), ma l'ho trovato molto interessante, poiché mancano le risorse nel campo del codice legacy (e in particolare nella mia lingua, il francese, ma è un'altra storia).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top