Esistono librerie o metodi per deridere il file system in C # per scrivere unit test? Nel mio caso attuale ho dei metodi che controllano l'esistenza di determinati file e leggono la data di creazione. Potrei aver bisogno di più di quello in futuro.
Esistono librerie o metodi per deridere il file system in C # per scrivere unit test? Nel mio caso attuale ho dei metodi che controllano l'esistenza di determinati file e leggono la data di creazione. Potrei aver bisogno di più di quello in futuro.
Risposte:
Modifica: installa il pacchetto NuGet System.IO.Abstractions.
Questo pacchetto non esisteva quando questa risposta era stata inizialmente accettata. La risposta originale è fornita per il contesto storico di seguito:
Potresti farlo creando un'interfaccia:
interface IFileSystem { bool FileExists(string fileName); DateTime GetCreationDate(string fileName); }e creando un'implementazione "reale" che utilizza System.IO.File.Exists () ecc. È quindi possibile prendere in giro questa interfaccia usando un framework di derisione; Raccomando Moq .
Modifica: qualcuno ha fatto questo e lo ha gentilmente pubblicato online qui .
Ho usato questo approccio per deridere DateTime.UtcNow in un'interfaccia IClock (davvero molto utile per i nostri test per essere in grado di controllare il flusso del tempo!), E più tradizionalmente, un'interfaccia ISqlDataAccess.
Un altro approccio potrebbe essere l'utilizzo di TypeMock , che consente di intercettare le chiamate alle classi e di interromperle. Ciò tuttavia costa denaro e dovrebbe essere installato sui PC dell'intero team e sul server di compilazione per funzionare, inoltre, apparentemente non funzionerà per il file System.IO., poiché non può stub mscorlib .
Si potrebbe anche semplicemente accettare che alcuni metodi non sono testabili in unità e testarli in una suite di test di integrazione / sistema separata a esecuzione lenta.
Questa libreria immaginaria esiste ora, esiste un pacchetto NuGet per System.IO.Abstractions , che estrae lo spazio dei nomi System.IO.
Esiste anche una serie di assistenti di test, System.IO.Abstractions.TestingHelpers che - al momento della stesura - è implementato solo parzialmente, ma è un ottimo punto di partenza.
Probabilmente dovrai creare un contratto per definire le cose che ti servono dal file system e poi scrivere un wrapper attorno a quelle funzionalità. A quel punto saresti in grado di deridere o stub l'implementazione.
Esempio:
interface IFileWrapper { bool Exists(String filePath); }
class FileWrapper: IFileWrapper
{
bool Exists(String filePath) { return File.Exists(filePath); }
}
class FileWrapperStub: IFileWrapper
{
bool Exists(String filePath)
{ return (filePath == @"C:\myfilerocks.txt"); }
}
La mia raccomandazione è di usare http://systemwrapper.codeplex.com/ in quanto fornisce wrapper per i tipi maggiormente utilizzati nello spazio dei nomi di sistema
Ho trovato le seguenti soluzioni a questo:
Finisco per usare tutti i metodi sopra, a seconda di cosa sto scrivendo. Ma la maggior parte delle volte finisco per pensare che l'astrazione sia sbagliata quando scrivo unit test che colpiscono l'IO.
Usando System.IO.Abstractions e System.IO.Abstractions.TestingHelpers così:
public class ManageFile {
private readonly IFileSystem _fileSystem;
public ManageFile(IFileSystem fileSystem){
_fileSystem = fileSystem;
}
public bool FileExists(string filePath){}
if(_fileSystem.File.Exists(filePath){
return true;
}
return false;
}
}
Nella tua classe di test, usi MockFileSystem () per simulare file e installi ManageFile come:
var mockFileSysteme = new MockFileSystem();
var mockFileData = new MockFileData("File content");
mockFileSysteme.AddFile(mockFilePath, mockFileData );
var manageFile = new ManageFile(mockFileSysteme);
Puoi farlo usando Microsoft Fakes senza la necessità di cambiare la tua base di codice, ad esempio perché era già congelato.
Generare prima un assembly falso per System.dll o qualsiasi altro pacchetto e quindi simulare i rendimenti previsti come in:
using Microsoft.QualityTools.Testing.Fakes;
...
using (ShimsContext.Create())
{
System.IO.Fakes.ShimFile.ExistsString = (p) => true;
System.IO.Fakes.ShimFile.ReadAllTextString = (p) => "your file content";
//Your methods to test
}
Sarebbe difficile deridere il file system in un test poiché le API dei file .NET non sono realmente basate su interfacce o classi estendibili che potrebbero essere derise.
Tuttavia, se hai il tuo livello funzionale per accedere al file system, potresti deriderlo in un test unitario.
In alternativa al derisione, considera solo la creazione di cartelle e file necessari come parte della configurazione del test e la loro eliminazione nel metodo di smontaggio.
Non sono sicuro di come derideresti il file system. Quello che potresti fare è scrivere una configurazione del dispositivo di prova che crei una cartella, ecc. Con la struttura necessaria per i test. Un metodo di smontaggio lo ripulirebbe dopo l'esecuzione dei test.
Modificato per aggiungere: nel pensarci un po 'di più, non penso che tu voglia deridere il file system per testare questo tipo di metodi. Se deridi il file system per restituire true se esiste un determinato file e lo usi nel tuo test di un metodo che controlla se quel file esiste, allora non stai testando molto di nulla. La derisione del file system sarebbe utile se si volesse testare un metodo che aveva una dipendenza dal file system ma l'attività del file system non era parte integrante del metodo in esame.
Per rispondere alla tua domanda specifica: No, non ci sono librerie che ti permetteranno di deridere chiamate di I / O di file (di cui sono a conoscenza). Ciò significa che un "corretto" test delle unità dei tuoi tipi richiederà di prendere in considerazione questa restrizione quando definisci i tuoi tipi.
Breve nota a margine su come definisco un "corretto" test unitario. Credo che i test unitari dovrebbero confermare che si ottiene l'output previsto (sia un'eccezione, un metodo di chiamata, ecc.) Fornito input noti. Ciò consente di impostare le condizioni di test dell'unità come un insieme di ingressi e / o stati di ingresso. Il modo migliore che ho trovato per farlo è usare i servizi basati sull'interfaccia e l'iniezione delle dipendenze in modo che ogni responsabilità esterna a un tipo sia fornita attraverso un'interfaccia passata tramite un costruttore o una proprietà.
Quindi, con questo in mente, torniamo alla tua domanda. Ho deriso le chiamate al file system creando IFileSystemServiceun'interfaccia insieme a FileSystemServiceun'implementazione che è semplicemente una facciata rispetto ai metodi del file system mscorlib. Il mio codice utilizza quindi IFileSystemServicei tipi anziché mscorlib. Questo mi permette di collegare il mio standard FileSystemServicequando l'applicazione è in esecuzione o di prendere in giro i IFileSystemServicetest della mia unità. Il codice dell'applicazione è lo stesso indipendentemente da come viene eseguito, ma l'infrastruttura sottostante consente di testarlo facilmente.
Riconoscerò che è un problema usare il wrapper attorno agli oggetti del file system mscorlib ma, in questi scenari specifici, vale la pena fare un lavoro extra poiché i test diventano molto più facili e affidabili.
Creare un'interfaccia e deriderla per i test è il modo più pulito di procedere. Tuttavia, in alternativa è possibile dare un'occhiata al framework Microsoft Moles .
La soluzione comune sta usando alcune API astratte del filesystem (come Apache Commons VFS per Java): tutta la logica dell'applicazione utilizza API e unit test è in grado di deridere il filesystem reale con l'implementazione di stub (emulazione in memoria o qualcosa del genere).
Per C # esiste l'API simile: NI.Vfs che è molto simile a Apache VFS V1. Contiene implementazioni predefinite sia per il filesystem locale che per il filesystem in memoria (l'ultimo può essere utilizzato in unità di test dalla scatola).
Andrei con la risposta di Jamie Ide. Non cercare di deridere cose che non hai scritto. Ci saranno tutti i tipi di dipendenze che non conoscevi: classi sigillate, metodi non virtuali ecc.
Un altro approccio sarebbe quello di avvolgere i metodi appropriati con qualcosa di derisorio. ad esempio, creare una classe chiamata FileWrapper che consente l'accesso ai metodi File ma è qualcosa che si può deridere.