Testare con tutte le dipendenze in atto è ancora importante, ma è più nel regno dei test di integrazione come ha detto Jeffrey Faust.
Uno degli aspetti più importanti del test unitario è rendere i test affidabili. Se non ti fidi che un test di superamento significhi davvero che le cose vanno bene e che un test fallito significa davvero un problema nel codice di produzione, i tuoi test non sono così utili come possono essere.
Per rendere affidabili i tuoi test, devi fare alcune cose, ma mi concentrerò solo su uno per questa risposta. Devi assicurarti che siano facili da eseguire, in modo che tutti gli sviluppatori possano eseguirli facilmente prima di eseguire il check-in del codice. "Facile da eseguire" significa che i tuoi test vengono eseguiti rapidamente e non è necessaria una vasta configurazione o installazione per farli andare avanti. Idealmente, chiunque dovrebbe essere in grado di controllare l'ultima versione del codice, eseguire subito i test e vederli passare.
Sottrarre dipendenze da altre cose (file system, database, servizi web, ecc.) Ti consente di evitare la necessità di configurazione e rende te e altri sviluppatori meno suscettibili alle situazioni in cui sei tentato di dire "Oh, i test falliscono perché non ho impostato la condivisione di rete. Vabbè. Li eseguirò più tardi. "
Se si desidera verificare ciò che si fa con alcuni dati, i test dell'unità per quel codice di logica aziendale non dovrebbero interessarsi di come si ottengono tali dati. Essere in grado di testare la logica principale dell'applicazione senza dipendere da elementi di supporto come i database è fantastico. Se non lo fai, ti stai perdendo.
PS Dovrei aggiungere che è sicuramente possibile progettare in modo eccessivo in nome della testabilità. Il test di guida dell'applicazione aiuta a mitigarlo. Ma in ogni caso, le cattive implementazioni di un approccio non rendono l'approccio meno valido. Qualcosa può essere usato in modo improprio e esagerato se non si continua a chiedere "perché lo sto facendo?" durante lo sviluppo.
Per quanto riguarda le dipendenze interne, le cose si fanno un po 'confuse. Il modo in cui mi piace pensarci è che voglio proteggere il più possibile la mia classe dal cambiamento per motivi sbagliati. Se ho una configurazione come questa ...
public class MyClass
{
private SomeClass someClass;
public MyClass()
{
someClass = new SomeClass();
}
// use someClass in some way
}
In genere non mi interessa come SomeClass
viene creato. Voglio solo usarlo. Se SomeClass cambia e ora richiede parametri per il costruttore ... non è un mio problema. Non avrei dovuto cambiare MyClass per adattarlo.
Ora, questo è solo toccare la parte del design. Per quanto riguarda i test unitari, voglio anche proteggermi dalle altre classi. Se sto testando MyClass, mi piace sapere per certo che non ci sono dipendenze esterne, che SomeClass a un certo punto non ha introdotto una connessione al database o qualche altro collegamento esterno.
Ma un problema ancora più grande è che so anche che alcuni dei risultati dei miei metodi si basano sull'output di alcuni metodi su SomeClass. Senza prendere in giro / stubbing SomeClass, non avrei modo di variare quell'input richiesto. Se sono fortunato, potrei essere in grado di comporre il mio ambiente all'interno del test in modo tale da innescare la risposta corretta da SomeClass, ma andare in questo modo introduce complessità nei miei test e li rende fragili.
Riscrivere MyClass per accettare un'istanza di SomeClass nel costruttore mi consente di creare un'istanza falsa di SomeClass che restituisce il valore desiderato (tramite un framework beffardo o con una simulazione manuale). In genere non devo introdurre un'interfaccia in questo caso. Se farlo o meno è in molti modi una scelta personale che può essere dettata dalla tua lingua preferita (ad esempio le interfacce sono più probabili in C #, ma sicuramente non ne avresti bisogno in Ruby).