2012-02-15 14 views
5

Stack di tecnologia: .NET 4, C#, NUnitTDD: È plausibile disporre di test di integrazione, ma nessun test unitario?

Sto tentando di applicare lo sviluppo basato su test a un nuovo progetto che esegue l'elaborazione delle immagini. Ho una classe base che contiene i metodi I/O di file condivisi e sottoclassi che eseguono vari algoritmi di elaborazione specifici. A quanto ho capito, i test unitari non toccano il file system o altri oggetti e simulano il comportamento laddove ciò si verifica. La mia classe base contiene solo accessor semplici e chiamate I/O di file system semplici.

public class BaseFile 
{ 
    public String Path { get; set; } 

    public BaseFile() 
    { 
     Path = String.Empty; 
    } 

    public BaseFile(String path) 
    { 
     if (!File.Exists(path)) 
     { 
      throw new FileNotFoundException("File not found.", path); 
     } 

     Path = path; 
    } 
} 

C'è qualche valore nel testare questi metodi? In caso affermativo, come potrei astrarre le chiamate al file system?

Un'altra domanda è come testare la sottoclasse che è specifica per un tipo di file immagine (~ 200 MB). Ho cercato il sito e trovato similarquestions, ma nessuno si occupa delle dimensioni del file con cui sto lavorando su questo progetto. È plausibile che una classe abbia test di integrazione (usando un "file dorato"), ma non test di unità? Come potrei seguire rigorosamente i metodi TDD e in primo luogo scrivere un test fallito in questo caso?

+4

Se TDD è difficile da applicare o inadeguato, non applicarlo. Non è una pallottola d'argento. – CharlesB

+1

@CharlesB, sono d'accordo. Sfortunatamente questo sentimento è spesso usato come scusa per non usare TDD quando è effettivamente giusto e vantaggioso farlo. A volte c'è una grande curva o un sacco di lavoro da fare, ma questo di solito paga. –

+1

@CharlesB Non sono sicuro che sia d'accordo. Il problema è che quindi questa classe che fa un po 'di lavoro non è testata, e quindi la tua fiducia nel cambiarla è diminuita. Il problema è che non è facile prendere in giro i metodi statici in System.IO, ma ciò non significa rinunciare ai test, significa solo che devi fare un po 'più di lavoro per renderli testabili. Questo è il motivo per cui MS introduce System.Web.HttpContextBase, per risolvere i problemi con non facilmente essere in grado di prendere in giro un HttpContext. Non abbiamo bisogno di testare httpContext, solo che il nostro codice interagisce correttamente con esso. – Andy

risposta

4

In risposta alla tua prima domanda, sì, c'è un valore nel testare questi metodi. Ho pubblicato una libreria che facilita esattamente quello senza colpire il file system: https://bitbucket.org/mylesmcdonnell/mpm.io/wiki/Home

In (non una) risposta alla seconda domanda Avrei bisogno di vedere qualche codice, ma ho il sospetto che potrebbe essere necessario prendere un simile approccio alla lib sopra. cioè; definire l'interfaccia, definire il proxy su concreto, definire factory per restituire proxy o mock.

+0

Grazie per aver postato questo codice sorgente. Segnalo come risposta la tua risposta perché hai fornito la migliore soluzione generale. – Noren

3

In che modo è possibile seguire rigorosamente i metodi TDD e in primo luogo scrivere un test non valido in questo caso?

Facile! Si prende in giro il file system :)

Questo può sembrare un sacco di lavoro, ma in genere è necessario solo implementare alcuni metodi ed espandere se necessario. Nel caso sopra ... ne hai solo bisogno.

public interface IFileStore 
{ 
    Boolean FileExists(String path); 
} 

L'idea è delegare il lavoro dei file dietro l'interfaccia e creare un'implementazione concreta per eseguire il sollevamento pesi. Fondamentalmente un Adapter pattern.

Non mi oppongo nemmeno al "DI uomo povero" per questo tipo di cose, poiché è possibile implementare un contenitore in un secondo momento se l'applicazione lo richiede. In questo caso ... probabilmente userete sempre il vero file system.

public class BaseFile 
{ 
    private IFileStore _fileStore 
    public IFileStore FileStore 
    { 
     get 
     { 
      return _fileStore ?? (_fileStore = new ConcreteFileStore()); 
     } 
     set 
     { 
      _fileStore = value; 
     } 
    } 

    //SNIP... 
} 

Ora avete un'implementazione testabile e non dovrete fare affidamento su alcun file "Golden".

+0

Grazie per il tuo post. Per ora, ho intenzione di implementare la tua soluzione finché non avrò bisogno di più delle chiamate System.IO, a quel punto userò il codice di Myles. – Noren

0

I test di integrazione hanno valore per conto proprio. Se prendi in giro il file system, come spiegato nella risposta di Josh, non sai per certo che il tuo codice verrà effettivamente eseguito in produzione. Il file system ha molti contratti nascosti che non sono banali da prendere in giro. Se il tuo simulato/falso mostra un comportamento leggermente diverso, il tuo codice potrebbe iniziare a fare affidamento su di esso senza che tu lo sappia.

Solo un test di integrazione è in grado di dire certe cose. (I test di integrazione hanno anche degli svantaggi!).

+0

corretto. Non sto insinuando che tu * non dovresti * fare test di integrazione. Piuttosto, quel TDD può ancora essere fatto anche su cose che normalmente non consideriamo come File System. – Josh

+0

Non intendevo fare svalutare la tua spiegazione. L'ho fatto riferimento perché era buono. – usr

1

C'è valore nel testare questi metodi

Anche se può sembrare come lavoro extra per l'aumento di banale in questo momento, l'aggiunta di prove come BaseFile dovrebbe gettare FileNotFoundException quando il file non esiste compie almeno due gol :

definire un elenco di comportamenti attesi

i nuovi arrivati ​​al progetto possono esaminare i nomi dei test per determinare come il vostro c le ragazze sono destinate a funzionare. Sapranno cosa aspettarsi in ogni situazione: un'eccezione, un risultato nullo, un risultato predefinito, ecc.

Ti costringe anche a pensare e definire in modo semplice come vuoi che le cose funzionino, al contrario di solo lanciando condizioni e eccezioni qua e là. Ciò dovrebbe tradursi in una filosofia molto coerente applicata al tuo progetto.

crescere una suite di test di regressione automatizzati

Si consideri che qualcuno vede qualche codice un'eccezione in una particolare condizione, ma penso che sia più saggio fare qualcosa di diverso (mangiare l'errore, ma aggiungere un nuovo Proprietà IsValid in modo che i consumatori possano sapere se la costruzione/inizializzazione ha avuto esito positivo). Se fanno un tale cambiamento, il test attirerà molto rapidamente l'attenzione sul cambiamento. C'era una decisione consapevole e intenzionale dietro il modo in cui le cose erano, e le persone potrebbero essere cresciute per fare affidamento sul comportamento esistente - questo cambiamento richiede ulteriori discussioni prima che possa essere accettato.

Per quanto riguarda la seconda parte della tua domanda, penso che Josh e Myles abbiano entrambi fornito già validi consigli.

+0

Hai fatto buoni punti. Credo che lascerò nei test per questi semplici metodi. – Noren

1

La simulazione di chiamate di file system semplici mediante le interfacce sembra un eccesso. Lo stesso vale per cose come l'ora corrente di derisione usando ITimeService. Io tendo ad usare Func o azione, perché è molto più semplice:

public static Func<string, bool> FileExists = System.IO.File.Exists; 
public static Func<DateTime> GetCurrentTime =() => DateTime.Now; 

Poiché questi sono pubblici posso prendere in giro il facilmente i test di unità. Il codice rimane semplice, non è necessario iniettare varie interfacce per cose semplici. Per gli stream di solito utilizzo MemoryStream in tets unitari.