Approcci graduali all'iniezione di dipendenza

11

Sto lavorando per rendere le mie classi testabili con l'unità, usando l'iniezione di dipendenza. Ma alcune di queste classi hanno molti clienti, e io non sono pronto a rifattenerli tutti per iniziare a passare le dipendenze ancora. Quindi sto provando a farlo gradualmente; mantenendo le dipendenze predefinite per ora, ma consentendo loro di essere sovrascritte per il test.

Un approccio che sto cercando è semplicemente spostare tutte le "nuove" chiamate nei loro metodi, ad esempio:

public MyObject createMyObject(args) {
  return new MyObject(args);
}

Poi nei miei test unitari, posso solo sottoclassi questa classe e sovrascrivere le funzioni create, quindi creano invece oggetti falsi.

È un buon approccio? Ci sono degli svantaggi?

Più in generale, va bene avere dipendenze codificate, purché sia possibile sostituirle per il test? So che l'approccio preferito è quello di richiederli esplicitamente nel costruttore, e mi piacerebbe arrivare lì alla fine. Ma mi chiedo se questo è un buon primo passo.

Uno svantaggio che mi è appena venuto in mente: se hai delle sottoclassi reali che devi testare, non puoi riutilizzare la sottoclasse di test che hai scritto per la classe genitore. Dovrai creare una sottoclasse di test per ciascuna sottoclasse reale e dovrà sovrascrivere le stesse funzioni di creazione.

    
posta JW01 29.01.2011 - 21:00
fonte

4 risposte

13

Questo è un buon approccio per iniziare. Nota che il più importante è coprire il tuo codice esistente con i test unitari; una volta che hai i test, puoi refactor più liberamente per migliorare ulteriormente il tuo design.

Quindi il punto iniziale non è quello di rendere il design elegante e lucido - solo per rendere verificabile l'unità di codice con le modifiche meno rischiose . Senza test unitari, devi essere più prudente e prudente per evitare di rompere il tuo codice. Queste modifiche iniziali potrebbero addirittura rendere il codice più goffo o brutto in alcuni casi, ma se ti consentono di scrivere i primi test di unità, alla fine sarai in grado di ridefinire il design ideale che hai in mente.

Il lavoro fondamentale da leggere su questo argomento è Funzionante in modo efficace con il codice legacy . Descrive il trucco che mostri sopra e molti altri ancora usando diverse lingue.

    
risposta data 29.01.2011 - 21:36
fonte
3

I Guice-gente raccomandano di usare

@Inject
public void setX(.... X x) {
   this.x = x;
}

per tutte le proprietà invece di aggiungere @Inject alla loro definizione. Ciò ti consentirà di trattarli come normali bean, che puoi new durante il test (e l'impostazione manuale delle proprietà) e @Inject in produzione.

    
risposta data 29.01.2011 - 21:36
fonte
3

Quando mi trovo di fronte a questo tipo di problema, identificherò ogni dipendenza della mia classe da testare e creerò un costruttore protetto che mi consentirà di passarli. Questo è in effetti lo stesso che creare un setter per ciascuno, ma preferisco dichiarare dipendenze nei miei costruttori in modo da non dimenticare di istanziarli in futuro.

Quindi la mia nuova classe testabile avrà 2 costruttori:

public MyClass() { 
  this(new Client1(), new Client2()); 
}

public MyClass(Client1 client1, Client2 client2) { 
  this.client1 = client1; 
  this.client2 = client2; 
}
    
risposta data 07.06.2013 - 20:05
fonte
2

Potresti considerare l'idioma "getter crea" finché non sei in grado di usare la dipendenza iniettata.

Ad esempio,

public class Example {
  private MyObject myObject=null;

  public MyObject getMyObject() {
    if (myObject==null) {
      myObject=new MyObject();
    } 
    return myObject;
  }

  public void setMyObject(MyObject myObject) {
    this.myObject=myObject;
  }

  private void myMethod() {
    if (getMyObject().doSomething()) {
      // Instance automatically created
    }
  }
}

Ciò consentirà di effettuare il refactoring internamente in modo da poter fare riferimento a myObject tramite il getter. Non devi mai chiamare new . In futuro, quando tutti i client sono configurati per consentire l'iniezione delle dipendenze, tutto ciò che devi fare è rimuovere il codice di creazione in un unico posto: il getter. Il setter ti permetterà di iniettare oggetti finti come richiesto.

Il codice sopra riportato è solo un esempio e espone direttamente lo stato interno. Dovresti considerare attentamente se questo approccio è appropriato per il tuo codebase.

Ti suggerisco inoltre di leggere " Lavorare efficacemente con il codice legacy " che contiene numerosi suggerimenti utili per avere successo con un importante refactoring.

    
risposta data 29.01.2011 - 21:37
fonte

Leggi altre domande sui tag