2013-03-10 23 views
6

suo essere un paio di mesi da quando sto lavorando con il codice Java legacy, questa sono alcune delle cose che ho a che fare con: copertura di testQual è la differenza tra una Seam e una Mock?

  • 0%.
  • Enormi funzioni in occasioni ne ho persino viste alcune con più di 300 righe di codice.
  • Un sacco di metodi privati ​​e in alcuni casi metodi statici.
  • Codice strettamente accoppiato.

All'inizio ero molto confuso, ho trovato difficile usare TDD nell'eredità. Dopo aver fatto i kata per settimane e aver esercitato le mie capacità di test e di presa in giro, la mia paura è diminuita e mi sento un po 'più fiducioso. Recentemente ho scoperto un libro chiamato: working effectivelly with legacy, non l'ho letto, ho solo dato un'occhiata al sommario e ho scoperto qualcosa di nuovo per me, The Seams. Apparentemente questo è molto importante quando si lavora nell'eredità.

Penso che questo Seams potrebbe aiutarmi molto a rompere le dipendenze e rendere testabile il mio codice in modo da poter aumentare la copertura del codice e rendere più preciso il test dell'unità.

Ma ho molti dubbi:

  • Qualcuno può spiegare la differenza tra una cucitura ed un finto?
  • Do Cuciture, rompono le regole TDD per quanto riguarda non toccare il codice di produzione, prima viene testato?
  • Potresti mostrarmi un semplice esempio che mette a confronto una Seam and a Mock?

Qui di seguito vorrei incollare un esempio che ho fatto oggi in cui ho tentato di rompere una dipendenza con l'obiettivo di rendere il codice testabile e infine aumentare la copertura del test. Ti sarei grato se potessi commentare un po 'se vedessi degli errori?

In questo modo il codice legacy sembrava all'inizio:

public class ABitOfLegacy 
{ 
    private String sampleTitle; 
    String output; 

    public void doSomeProcessing(HttpServletRequest request) { 
    String [] values = request.getParameterValues(sampleTitle); 
     if (values != null && values.length > 0) 
     { 
      output = sampleTitle + new Date().toString() + values[0]; 
     } 

    } 
} 

Se ho solo aggiungere una prova di unità che chiama questo metodo e afferma che uscita variabile, ha un certo valore dopo la chiamata, poi ho farebbe un errore, perché non sono un test unitario, farei test di integrazione. Quindi quello che devo fare, è eliminare la dipendenza che ho nel parametro. Per farlo, sostituisco il parametro con un'interfaccia:

public class ABitOfLegacy 
{ 
    private String sampleTitle; 
    String output; 

    public void doSomeProcessing(ParameterSource request) { 
    String [] values = request.getParameters(sampleTitle); 
     if (values != null && values.length > 0) 
     { 
      output = sampleTitle + new Date().toString() + values[0]; 
     } 
    } 

} 

In questo modo l'interfaccia si presenta come:

public interface ParameterSource { 
    String[] getParameters(String name); 
} 

La prossima cosa che faccio, è creare la mia propria implementazione di tale interfaccia, ma io includono il HttpServletRequest come una variabile globale e implementare il metodo dell'interfaccia utilizzando il metodo/s di HttpServletRequest:

public class HttpServletRequestParameterSource implements ParameterSource { 

    private HttpServletRequest request; 

    public HttpServletRequestParameterSource(HttpServletRequest request) { 
     this.request = request; 
    } 

    public String[] getParameters(String name) { 
     return request.getParameterValues(name); 
    } 

} 

Fino a questo punto, penso che tutte le modifiche su il codice di produzione era sicuro. Ora creo il Seam nel mio pacchetto di test. Se ho capito bene, ora sono in grado di cambiare in sicurezza il comportamento della Seam.Questo è come lo faccio:

public class FakeParameterSource implements ParameterSource { 

    public String[] values = {"ParamA","ParamB","ParamC"}; 

    public String[] getParameters(String name) { 
     return values; 
    } 
} 

E il passo finale, sarebbe quella di ottenere il sostegno della Seam, per testare la behavoir originale del metodo.

import org.junit.Before; 
import org.junit.Test; 
import static org.junit.Assert.*; 
import static org.mockito.Mockito.*; 
import code.ABitOfLegacyRefactored; 
import static org.hamcrest.Matchers.*; 

public class ABitOfLegacySpecification { 

    private ABitOfLegacy aBitOfLegacy; 
    private String EMPTY = null; 

    @Before 
    public void initialize() { 
     aBitOfLegacy = new ABitOfLegacy(); 
    } 

    @Test 
    public void 
    the_output_gets_populated_when_the_request_is_not_empty 
    () { 
     FakeParameterSource fakeParameterSource = new FakeParameterSource(); 
     aBitOfLegacy.doSomeProcessing(fakeParameterSource); 
     assertThat(aBitOfLegacy.output,not(EMPTY)); 
    } 

    @Test(expected=NullPointerException.class) 
    public void 
    should_throw_an_exception_if_the_request_is_null 
    () { 
     aBitOfLegacy.doSomeProcessing(null); 
    } 
} 

Questo mi darà una copertura di prova del 100%. Apprezzo i tuoi pensieri:

  • Ho interrotto la dipendenza correttamente?
  • I test di unità mancano qualcosa?
  • Cosa si può fare meglio?
  • Questo esempio è abbastanza buono da aiutarmi a capire la differenza tra una Seam and a Mock?
  • Come potrebbe un finto aiutarmi qui se non utilizzo la cucitura?
+0

Le mock non sostituiscono le cuciture. – Gishu

+1

Dovresti prendere quel libro e leggerlo. –

risposta

4

cuciture

A seam è un luogo che consente di modificare il comportamento senza modificare il codice.

Nel tuo esempio, il seguente è un esempio di una giuntura Oggetto (se non sbaglio). Ti permette di passare in un oggetto diverso senza dover cambiare il codice. quindi è un tipo di cucitura.

public void doSomeProcessing(ParameterSource request) {..} 

Rendendo il parametro un tipo astratto (anziché una classe concreta), è stata introdotta una cucitura. La cucitura ora consente di modificare il comportamento del metodo senza modificarne il codice, ad esempio nel punto di invocazione, posso passare in un oggetto diverso e fare in modo che il metodo faccia qualcos'altro.

Mocks

Ora, invece di creare il falso personalizzato (creazione di un sottotipo dell'interfaccia), si potrebbe utilizzare un framework Mock a fare qualcosa di simile

Mocks supportano anche affermando se i metodi specifici sono stati chiamato su di esso, la corrispondenza degli argomenti e altre funzionalità nifty da utilizzare nei test. Meno codice di test da mantenere. I mock sono utilizzati principalmente per affermare che viene effettuata una chiamata specifica a una dipendenza. Nel tuo esempio, sembra che tu abbia bisogno di un Stub, vuoi solo restituire un valore predefinito.

Perdono mio arrugginito JMock ..

@Test 
    public void 
    the_output_does_not_get_populated_when_the_request_is_empty 
    () { 
     Mockery context = new Mockery(); 
     final ParameterSource mockSource = context.mock(ParameterSource.class) 

context.checking(new Expectations(){{ 
    oneOf(mockSource).getParameters(); 
      will(returnValue(new string[]{"ParamA","ParamB","ParamC"}); 
}}); 
     aBitOfLegacy.populate(mockSource); 
     assertThat(aBitOfLegacy.output,not(EMPTY)); 
    } 

in .Net

var mockSource = new Mock<ParameterSource>(); 
mockSource.Setup(src => src.GetParameters()) 
      .Returns(new []{"ParamA","ParamB","ParamC"}); 
+0

Sono ancora un po 'confuso. Pensi che il mio esempio sopra possa essere testato usando mock invece di cuciture? Con le strutture moderne possiamo addestrare i mock a comportarsi in certi modi anche. Inoltre, manteniamo le prove, le cuciture saranno più difficili da mantenere? – sfrj

+0

Oops: il mio post è un po 'confuso. Aggiornato con un esempio per i mock ... potrebbe non essere compilato - java non è nativo per me. Per usare un mock, hai ancora bisogno di una cucitura - di solito una cucitura di un oggetto come un paramotore o un argomento. Non c'è "invece" - la cucitura ti consente di sostituire un oggetto diverso (ad es. Per il test). Le mazze semplificano i test di interazione. Salvano lo sforzo di creare oggetti falsi personalizzati per testarli e mantenerli. Offrono anche una migliore segnalazione. – Gishu

+0

Capisco la tua spiegazione. Grazie per l'esempio di prova è bello :) Fino ad ora non ho capito l'importanza delle cuciture quando, test unitario. – sfrj

7

Una cucitura è un luogo nel codice che è possibile inserire una modifica nel comportamento. Hai creato una cucitura quando hai impostato l'iniezione della tua dipendenza.

Un modo per sfruttare una cucitura è inserire una sorta di falso. I falsi possono essere arrotolati a mano, come nel tuo esempio, o essere creati con uno strumento, come Mockito.

Quindi, un finto è un tipo di falso e un falso viene spesso utilizzato sfruttando una cucitura.

Per quanto riguarda i test e il modo in cui hai rotto la dipendenza, è praticamente come avrei fatto.

+0

Bella spiegazione, breve ma accurata. Grazie. +1 – sfrj

Problemi correlati