Come si fa a deridere il file system in C # per i test unitari?


149

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.


1
Sembra un duplicato di molti altri, tra cui: stackoverflow.com/questions/664277/… .
John Saunders,


2
@Mitch: il più delle volte, è sufficiente inserire i dati nel file system e lasciare che i test unitari eseguano il loro corso. Tuttavia, ho riscontrato metodi che eseguono molte operazioni di I / O e l'impostazione dell'ambiente di test per tali metodi è notevolmente semplificata utilizzando un finto file system.
Steve Guidi,

Ho scritto github.com/guillaume86/VirtualPath a tale scopo (e altro), è ancora WIP e l'API cambierà sicuramente ma già funziona e alcuni test sono inclusi.
Guillaume86,

Risposte:


154

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.


1
Secondo me, creare un'interfaccia come quella descritta da Matt è la strada da percorrere. Ho anche scritto uno strumento che genera tali interfacce per te, che è utile quando cerchi di deridere classi statiche e / o sigillate, o metodi che non sono deterministici (ovvero orologi e generatori di numeri casuali). Vedere jolt.codeplex.com per ulteriori informazioni.
Steve Guidi,

Sembra che il repository nell'articolo di riferimento sia stato eliminato / spostato senza preavviso. Tuttavia, sembra esserci un pacchetto nuget dei suoi sforzi qui: nuget.org/packages/mscorlib-mock
Mike-E

Typemock ha delle restrizioni su quali tipi sono falsi ma (almeno nella versione corrente a partire da ottobre 2017) puoi sicuramente falsificare la classe statica File. L'ho appena verificato da solo.
Ryan Rodemoyer,

Puoi riassumere alcune suite di test di integrazione?
Ozkan,

83

Install-Package System.IO.Abstractions

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.


3
Penso che standardizzare questa astrazione già costruita sia la scommessa migliore. Non ho mai sentito parlare di quella biblioteca, quindi grazie mille per il testa a testa.
julealgon,

PM sta per gestore pacchetti ... per aprire ... Strumenti> Gestione pacchetti NuGet> Console Gestione pacchetti
thedanotto

11

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"); }
}

5

La mia raccomandazione è di usare http://systemwrapper.codeplex.com/ in quanto fornisce wrapper per i tipi maggiormente utilizzati nello spazio dei nomi di sistema


Attualmente sto usando questa libreria e ora che ho scoperto che le sue astrazioni per cose come FileStream non includono IDisposable, sto cercando un sostituto. Se la libreria non mi consente di smaltire correttamente gli stream, non posso raccomandarlo (o usarlo) per gestire questo tipo di operazioni.
James Nail,

1
IFileStreamWrap di SystemWrapper implementa subito IDisposable.
tster

systemwrapper è solo .net framework, causerà strani problemi se usato con .netcore
Adil H. Raza

3

Ho trovato le seguenti soluzioni a questo:

  • Scrivi test di integrazione, non test unitari. Perché ciò funzioni è necessario un modo semplice per creare una cartella in cui è possibile scaricare materiale senza preoccuparsi che altri test interferiscano. Ho una semplice classe TestFolder che può creare una cartella unica per metodo di prova da utilizzare.
  • Scrivi un file System.IO.File beffardo. Cioè creare un IFile.cs . Trovo che l'uso di questo spesso finisca con dei test che dimostrano semplicemente che puoi scrivere dichiarazioni beffarde, ma lo usano quando l'uso di I / O è piccolo.
  • Esamina il tuo livello di astrazione ed estrai il file IO dalla classe. Crea un'interfaccia per questo. Il resto usa test di integrazione (ma questo sarà molto piccolo). Ciò differisce da quanto sopra in quello invece di fare file.Per leggere l'intento, dico ioThingie.loadSettings ()
  • System.IO.Abstractions . Non l'ho ancora usato, ma è quello con cui sono più entusiasta di giocare.

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.


4
Il collegamento a IFile.cs è interrotto.
Mike-E,

3

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);

2

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
}

1

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.


1

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.


1

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.


1

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 .


0

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).


-1

Attualmente utilizziamo un motore di dati proprietario e la sua API non è esposta come interfacce, quindi difficilmente possiamo testare l'unità del nostro codice di accesso ai dati. Poi ho seguito anche l'approccio di Matt e Joseph.


-2

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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.