2009-09-23 9 views
6

Sto lavorando con un codice esistente, cercando di aggiungerlo e aumentare i test di unità per esso. Ma ci sono alcuni problemi con il test del codice.Progettazione di costruttori per testabilità

Costruttore originale:

public Info() throws Exception 
{ 
    _ServiceProperties = new ServiceProperties(); 
    _SshProperties = new SshProperties(); 
} 

Sono consapevole che questo è un male, e ovviamente non verificabile. In un ambiente junit, questa classe non creerà ogni volta poiché non sarà in grado di trovare le proprietà necessarie per costruirsi. Ora, sono consapevole che questa classe sarebbe molto più testabile con il semplice cambiamento di spostamento di qualsiasi cosa preceduta da "nuovo" come parametro.

così finisco con:

nuovo costruttore:

public Info(ServiceProperties srvProps, SshProperties sshProps) throws Exception 
{ 
    _ServiceProperties = srvProps; 
    _SshProperties = sshProps; 
} 

che mi permette di unità di test correttamente questa classe Info. Il problema, però, è ora tutto quel lavoro è spinto a qualche altra classe:

Metodo qualche altra classe:

public void useInfo() throws Exception 
{ 
    ServiceProperties srvProps = new ServiceProperties(); 
    SshProperties sshProps = new SshProperties(); 
    Info info = new Info(srvProprs, sshProprs); 
    doStuffWithInfo(info); 
} 

Ora, questo metodo non è verificabile. Tutto quello che sono riuscito a fare è spingere dove si verificano le costruzioni di questi oggetti Property, e da qualche altra parte qualche pezzo di codice sarà bloccato a dover chiamare "nuovo".

Ecco il problema per me: non riesco a capire come rompere questa catena di eventi semplicemente spingendo queste "nuove" chiamate da qualche altra parte. Cosa mi manca?

risposta

7

Osservare l'utilizzo di un framework Iniezione delle dipendenze come Spring. Questa applicazione di Inversion of Control significa che ognuno dei tuoi tipi può evitare il trabocchetto che hai visto, lasciando la configurazione in "wire" dei componenti.

Questo introduction to Spring (pdf) offre una panoramica completa di Spring. Il primo o il secondo capitolo dovrebbe essere sufficiente per spiegare i concetti.

vedere anche Inversion of Control Containers and the Dependency Injection pattern da Martin Fowler

+0

Si può anche dare un'occhiata al nostro Materiale del corso di primavera: http://www.trainologic.org/courses/info/2 –

0

La risposta più semplice è primavera. Comunque un'altra risposta è di mettere la tua roba di configurazione in JNDI. Spring in qualche modo è una risposta migliore, soprattutto se non si dispone di nulla che cambi a seconda dell'ambiente.

2

Avete l'idea giusta. Forse questo ti aiuterà. Ti raccomando di seguire due regole per tutte le tue classi di significatività, dove "di significato" significa che se non segui i passaggi sarà più difficile testare, riutilizzare o mantenere la classe. Ecco le regole:

  1. mai istanziare o auto-acquisire una dipendenza
  2. sempre programma per interfacce

Hai un inizio al 1 regola #. Hai modificato la tua classe Info per non creare più le sue dipendenze. Per "dipendenza" intendo altre classi, dati di configurazione caricati da file di proprietà o altro, ecc.Quando dipendi da come viene creata un'istanza, stai vincolando la tua classe e rendendola più difficile da testare, riutilizzare e mantenere. Quindi, anche se una dipendenza viene creata tramite una fabbrica o un singleton, non è necessario che la classe la crei. Avere qualcos'altro chiama create() o getInstance() o qualsiasi altra cosa e inviarlo.

Quindi hai scelto il "qualcos'altro" per essere la classe che usa la tua classe e hai capito che c'è un cattivo odore. Il rimedio è invece di avere il punto di accesso all'applicazione per creare un'istanza di tutte le dipendenze. In una tradizionale app java, questo è il tuo metodo main(). se ci pensate, creare istanze di classi e collegarle tra loro, o "collegarle" insieme, è un tipo speciale di logica: logica di "assemblaggio delle applicazioni". È meglio diffondere questa logica in tutto il codice o raccoglierla in un posto per mantenerla più facilmente? La risposta è che la raccolta in un posto è migliore, non solo per la manutenzione, ma l'atto di farlo trasforma tutte le classi di significatività in componenti più utili e flessibili.

Nel main() o equivalente di main(), è necessario creare tutti gli oggetti necessari, passandoli nei setter e nei costruttori degli altri per "collegarli" insieme. I tuoi test unitari li collegherebbero in un modo diverso, passando oggetti finti o cose simili. L'atto di fare tutto questo è chiamato "iniezione di dipendenza". Dopo aver fatto ciò che ho detto, probabilmente avrai un brutto metodo main(). È qui che uno strumento di iniezione delle dipendenze può aiutarti e rendere il tuo codice infinitamente più flessibile. Lo strumento che suggerirei quando arriverai a questo punto, come altri hanno suggerito, è Spring.

La regola meno importante # 2, sempre programmata per le interfacce, è ancora molto importante perché elimina tutte le dipendenze dall'implementazione, rendendo il riutilizzo molto più semplice, senza menzionare altri strumenti come framework di oggetti simulati, framework ORM, ecc. anche.

1

Anche quadri di iniezione di dipendenza come Spring, Guice, PicoContainer, ecc. Hanno bisogno di una sorta di boostrap in modo da creare sempre qualcosa.

Ti suggerisco di utilizzare un provider/factory che restituisce un'istanza configurata di classe. Questo ti permetterebbe di uscire dalla gerarchia di "creazione".

0

Lasciate che la classe dell'altro abbia troppa conoscenza della classe Info e delle sue dipendenze. Un framework di iniezione delle dipendenze utilizza una classe di provider. Utilizzando i tipi generici si può fare un fornitore di oggetti Info:

interface Provider<T> { 
    T get(); 
} 

Se il vostro qualche-altro-class prendere un provider <Info> nel suo costruttore il metodo sarà simile:

public void useInfo() throws Exception 
{ 
    Info info = infoProvider.get(); 
    doStuffWithInfo(info); 
} 

Questo ha rimosso la costruzione di classi concrete dal tuo codice. Il passaggio successivo consiste nel creare informazioni in un'interfaccia per semplificare la creazione di una simulazione per il caso specifico di test unitario.

E sì, questo spingerà e spingerà ulteriormente il codice di costruzione dell'oggetto. Porterà a qualche modulo che descrive solo come collegare le cose insieme. Le "regole" per tale codice sono che dovrebbe essere privo di istruzioni e cicli condizionali.

Consiglio di leggere Misko Heverys blog. Tutte le presentazioni sono utili e stampano la guida per scrivere un codice verificabile come un piccolo regolamento una volta capito che le regole sono una buona cosa.

1

I costruttori non sono errati e il problema non riguarda il momento in cui il codice viene eseguito, si tratta di ciò che tutti gli altri hanno menzionato: Iniezione di dipendenza. È necessario creare oggetti di simulazione SshProperties per creare un'istanza dell'oggetto. Il modo più semplice (assumendo che la classe non è contrassegnato come finale) è quello di estendere la classe:

public class MockSshProperties extends SshProperties { 
    // implemented methods 
} 

È possibile utilizzare quadri finto come Mockito:

public class Info { 

    private final sshProps; 
    private final serviceProps; 

    public Info() { 
     this(new SshProperties(), new ServiceProperties()); 
    } 

    public Info(SshProperties arg1, ServiceProperties arg2) { 
     this.sshProps = arg1; 
     this.serviceProps = arg2 
    } 
} 

public class InfoTester 
{ 
    private final static SshProperties sshProps = mock(SshProperties.class); 
    private final static ServiceProperties serviceProps = mock(ServiceProperties.class); 
    static { 
     when(sshProps.someGetMethod("something")).thenReturn("value"); 
    } 

    public static void main(String[] args) { 
     Info info = new Info(sshProps, serviceProps); 
     //do stuff 
    } 
} 
Problemi correlati