Wie verspotten Sie das Dateisystem in C # für Unit-Tests?

148

Gibt es Bibliotheken oder Methoden, um das Dateisystem in C # zu verspotten und Unit-Tests zu schreiben? In meinem aktuellen Fall habe ich Methoden, die prüfen, ob eine bestimmte Datei vorhanden ist, und das Erstellungsdatum lesen. Vielleicht brauche ich in Zukunft mehr.

pupeno
quelle
1
Dies sieht aus wie ein Duplikat mehrerer anderer, einschließlich: stackoverflow.com/questions/664277/… .
John Saunders
Vielleicht versuchen Sie es mit pex ( research.microsoft.com/en-us/projects/pex/filesystem.pdf )
Tinus
2
@Mitch: Meistens reicht es aus, Daten im Dateisystem abzulegen und Unit-Tests laufen zu lassen. Ich bin jedoch auf Methoden gestoßen, die viele E / A-Vorgänge ausführen, und das Einrichten der Testumgebung für solche Methoden wird durch die Verwendung eines Scheindateisystems erheblich vereinfacht.
Steve Guidi
Ich habe github.com/guillaume86/VirtualPath für diesen Zweck (und mehr) geschrieben, es ist immer noch WIP und die API wird sich sicherlich ändern, aber es funktioniert bereits und einige Tests sind enthalten.
Guillaume86

Antworten:

154

Bearbeiten: Installieren Sie das NuGet-Paket System.IO.Abstractions.

Dieses Paket existierte nicht, als diese Antwort ursprünglich akzeptiert wurde. Die ursprüngliche Antwort wird für den historischen Kontext unten bereitgestellt:

Sie können dies tun, indem Sie eine Schnittstelle erstellen:

interface IFileSystem {
    bool FileExists(string fileName);
    DateTime GetCreationDate(string fileName);
}

und Erstellen einer "echten" Implementierung, die System.IO.File.Exists () usw. verwendet. Sie können diese Schnittstelle dann mithilfe eines Verspottungsframeworks verspotten. Ich empfehle Moq .

Bearbeiten: Jemand hat dies getan und es freundlicherweise hier online gestellt .

Ich habe diesen Ansatz verwendet, um DateTime.UtcNow in einer IClock-Schnittstelle (wirklich sehr nützlich für unsere Tests, um den Zeitfluss steuern zu können!) Und traditionell in einer ISqlDataAccess-Schnittstelle zu verspotten.

Ein anderer Ansatz könnte darin bestehen, TypeMock zu verwenden. Auf diese Weise können Sie Aufrufe an Klassen abfangen und diese löschen . Dies kostet jedoch Geld und müsste auf den PCs Ihres gesamten Teams und Ihrem Build-Server installiert werden, um ausgeführt zu werden. Außerdem funktioniert es anscheinend nicht für die System.IO.File, da es mscorlib nicht stubben kann .

Sie können auch einfach akzeptieren, dass bestimmte Methoden nicht Unit-testbar sind, und sie in einer separaten Suite für langsame Integrations- / Systemtests testen.

Matt Howells
quelle
1
Meiner Meinung nach ist das Erstellen einer Schnittstelle, wie Matt sie hier beschreibt, der richtige Weg. Ich habe sogar ein Tool geschrieben, das solche Schnittstellen für Sie generiert. Dies ist hilfreich, wenn Sie versuchen, statische und / oder versiegelte Klassen oder nicht deterministische Methoden (z. B. Uhren und Zufallszahlengeneratoren) zu verspotten. Weitere Informationen finden Sie unter jolt.codeplex.com .
Steve Guidi
Es sieht so aus, als ob das Repo in dem Artikel, auf den verwiesen wird, ohne vorherige Ankündigung gelöscht / verschoben wurde. Es scheint jedoch ein Nuget-Paket seiner Bemühungen hier zu geben: nuget.org/packages/mscorlib-mock
Mike-E
Typemock hat zwar Einschränkungen hinsichtlich der fälschbaren Typen, aber (zumindest in der aktuellen Version ab Oktober 2017) können Sie die statische Klasse "Datei" definitiv fälschen. Ich habe das gerade selbst überprüft.
Ryan Rodemoyer
Können Sie einige Integrationstestsuiten zusammenfassen?
Ozkan
83

Install-Package System.IO.Abstractions

Diese imaginäre Bibliothek existiert jetzt, es gibt ein NuGet-Paket für System.IO.Abstractions , das den System.IO-Namespace abstrahiert.

Es gibt auch eine Reihe von Testhelfern, System.IO.Abstractions.TestingHelpers, die zum Zeitpunkt des Schreibens nur teilweise implementiert sind, aber einen sehr guten Ausgangspunkt darstellen.

Binärer Worrier
quelle
3
Ich denke, dass die Standardisierung um diese bereits gebaute Abstraktion die beste Wahl ist. Ich habe noch nie von dieser Bibliothek gehört, also vielen Dank für das Heads-up.
Julealgon
PM steht für Paketmanager .. zu öffnen ... Tools> NuGet Package Manager>
Paketmanager-
11

Sie müssen wahrscheinlich einen Vertrag erstellen, um zu definieren, welche Dinge Sie vom Dateisystem benötigen, und dann einen Wrapper um diese Funktionen schreiben. An diesem Punkt können Sie die Implementierung verspotten oder stummschalten.

Beispiel:

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

Ich empfehle, http://systemwrapper.codeplex.com/ zu verwenden, da es Wrapper für die am häufigsten verwendeten Typen im System-Namespace bereitstellt

adeel41
quelle
Ich verwende derzeit diese Bibliothek und nachdem ich festgestellt habe, dass ihre Abstraktionen für Dinge wie FileStream IDisposable nicht enthalten, suche ich nach einem Ersatz. Wenn die Bibliothek es mir nicht erlaubt, Streams ordnungsgemäß zu entsorgen, kann ich sie nicht empfehlen (oder verwenden), um diese Art von Vorgängen zu handhaben.
James Nail
1
IFileStreamWrap von SystemWrapper implementiert jetzt IDisposable.
Tster
systemwrapper ist nur ein .net-Framework. Es wird seltsame Probleme verursachen, wenn es mit .netcore
Adil H. Raza verwendet wird.
3

Ich bin auf folgende Lösungen gestoßen:

  • Schreiben Sie Integrationstests, keine Komponententests. Damit dies funktioniert, benötigen Sie eine einfache Möglichkeit, einen Ordner zu erstellen, in dem Sie Inhalte sichern können, ohne sich um andere störende Tests sorgen zu müssen. Ich habe eine einfache TestFolder- Klasse, die einen eindeutigen Ordner pro Testmethode erstellen kann.
  • Schreiben Sie eine verspottbare System.IO.File. Das heißt, erstellen Sie eine IFile.cs . Ich finde, dass die Verwendung dieser Funktion häufig zu Tests führt, die einfach beweisen, dass Sie spöttische Anweisungen schreiben können, diese jedoch verwenden, wenn die E / A-Nutzung gering ist.
  • Untersuchen Sie Ihre Abstraktionsebene und extrahieren Sie die Datei-E / A aus der Klasse. Die erstellen dafür eine Schnittstelle. Der Rest verwendet Integrationstests (dies ist jedoch sehr klein). Dies unterscheidet sich von oben darin, dass Sie nicht file.Read schreiben, sondern ioThingie.loadSettings ()
  • System.IO.Abstraktionen . Ich habe das noch nicht benutzt, aber es ist das, mit dem ich am meisten aufgeregt bin.

Am Ende verwende ich alle oben genannten Methoden, je nachdem, was ich schreibe. Aber die meiste Zeit denke ich, dass Abstraktion falsch ist, wenn ich Unit-Tests schreibe, die die E / A treffen.

Michael Lloyd Lee mlk
quelle
4
Der Link zu IFile.cs ist unterbrochen.
Mike-E
3

Mit System.IO.Abstractions und System.IO.Abstractions.TestingHelpers wie folgt :

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

In Ihrer Testklasse verwenden Sie MockFileSystem (), um Dateien zu verspotten, und Sie instanziieren ManageFile wie folgt:

var mockFileSysteme = new MockFileSystem();
var mockFileData = new MockFileData("File content");
mockFileSysteme.AddFile(mockFilePath, mockFileData );
var manageFile = new ManageFile(mockFileSysteme);
Olivier Martial Soro
quelle
2

Sie können dies mit Microsoft Fakes tun, ohne Ihre Codebasis ändern zu müssen, z. B. weil sie bereits eingefroren war.

Zuerst ein gefälschte erzeugen Baugruppe für System.dll - oder ein anderes Paket und dann erwartete Renditen verspotten , wie 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
}
Bahadır İsmail Aydın
quelle
1

Es wäre schwierig, das Dateisystem in einem Test zu verspotten, da die .NET-Datei-APIs nicht wirklich auf Schnittstellen oder erweiterbaren Klassen basieren, die verspottet werden könnten.

Wenn Sie jedoch über eine eigene Funktionsschicht für den Zugriff auf das Dateisystem verfügen, können Sie dies in einem Komponententest verspotten.

Als Alternative zum Verspotten sollten Sie nur die Ordner und Dateien erstellen, die Sie im Rahmen Ihres Test-Setups benötigen, und diese in Ihrer Teardown-Methode löschen.

LBushkin
quelle
1

Ich bin mir nicht sicher, wie Sie das Dateisystem verspotten würden. Was Sie tun können, ist ein Test-Fixture-Setup zu schreiben, das einen Ordner usw. mit der erforderlichen Struktur für die Tests erstellt. Eine Teardown-Methode würde es nach dem Testlauf bereinigen.

Bearbeitet, um hinzuzufügen: Wenn Sie etwas mehr darüber nachdenken, möchten Sie das Dateisystem nicht verspotten, um diese Art von Methoden zu testen. Wenn Sie das Dateisystem verspotten, um true zurückzugeben, wenn eine bestimmte Datei vorhanden ist, und dies in Ihrem Test einer Methode verwenden, die prüft, ob diese Datei vorhanden ist, testen Sie nicht viel von irgendetwas. Das Verspotten des Dateisystems ist nützlich, wenn Sie eine Methode testen möchten, die vom Dateisystem abhängig ist, deren Dateisystemaktivität jedoch nicht in die zu testende Methode integriert ist.

Jamie Ide
quelle
1

Um Ihre spezielle Frage zu beantworten: Nein, es gibt keine Bibliotheken, mit denen Sie Datei-E / A-Aufrufe verspotten können (von denen ich weiß). Dies bedeutet, dass Sie für "ordnungsgemäße" Unit-Tests Ihrer Typen diese Einschränkung berücksichtigen müssen, wenn Sie Ihre Typen definieren.

Kurze Randnotiz darüber, wie ich einen "richtigen" Komponententest definiere. Ich glaube, dass Unit-Tests bestätigen sollten, dass Sie die erwartete Ausgabe erhalten (sei es eine Ausnahme, ein Aufruf einer Methode usw.), sofern bekannte Eingaben vorliegen. Auf diese Weise können Sie Ihre Unit-Test-Bedingungen als eine Reihe von Eingängen und / oder Eingangszuständen einrichten. Der beste Weg, dies zu tun, besteht darin, schnittstellenbasierte Dienste und Abhängigkeitsinjektion zu verwenden, sodass jede Verantwortung außerhalb eines Typs über eine Schnittstelle bereitgestellt wird, die über einen Konstruktor oder eine Eigenschaft übergeben wird.

In diesem Sinne zurück zu Ihrer Frage. Ich habe Dateisystemaufrufe verspottet, indem ich eine IFileSystemServiceSchnittstelle zusammen mit einer FileSystemServiceImplementierung erstellt habe, die lediglich eine Fassade über die Dateisystemmethoden von mscorlib darstellt. Mein Code verwendet dann IFileSystemServiceeher den Typ als den mscorlib-Typ. Auf diese Weise kann ich meinen Standard anschließen, FileSystemServicewenn die Anwendung ausgeführt wird, oder die IFileSystemServicein meinen Komponententests verspotten . Der Anwendungscode ist unabhängig von der Ausführung identisch, aber die zugrunde liegende Infrastruktur ermöglicht das einfache Testen dieses Codes.

Ich werde anerkennen, dass es schwierig ist, den Wrapper um die mscorlib-Dateisystemobjekte zu verwenden, aber in diesen speziellen Szenarien lohnt sich die zusätzliche Arbeit, da das Testen so viel einfacher und zuverlässiger wird.

akmad
quelle
1

Das Erstellen und Verspotten einer Schnittstelle zum Testen ist der sauberste Weg. Als Alternative können Sie sich jedoch das Microsoft Moles- Framework ansehen .

Konamiman
quelle
0

Die übliche Lösung ist die Verwendung einer abstrakten Dateisystem-API (wie Apache Commons VFS für Java): Die gesamte Anwendungslogik verwendet eine API, und der Komponententest kann ein reales Dateisystem mit einer Stub-Implementierung (In-Memory-Emulation oder ähnliches) verspotten.

Für C # existiert die ähnliche API: NI.Vfs, die Apache VFS V1 sehr ähnlich ist. Es enthält Standardimplementierungen sowohl für das lokale Dateisystem als auch für das In-Memory-Dateisystem (die letzte kann in Komponententests aus der Box verwendet werden).

Vitaliy Fedorchenko
quelle
-1

Wir verwenden derzeit eine proprietäre Daten-Engine und ihre API wird nicht als Schnittstellen verfügbar gemacht, sodass wir unseren Datenzugriffscode kaum einem Unit-Test unterziehen können. Dann ging ich auch mit Matt und Joseph vor.

Tien Do.
quelle
-2

Ich würde der Antwort von Jamie Ide folgen. Versuchen Sie nicht, Dinge zu verspotten, die Sie nicht geschrieben haben. Es wird alle Arten von Abhängigkeiten geben, von denen Sie nichts wussten - versiegelte Klassen, nicht virtuelle Methoden usw.

Ein anderer Ansatz wäre, die entsprechenden Methoden mit etwas zu verpacken, das verspottbar ist. Erstellen Sie beispielsweise eine Klasse namens FileWrapper, die den Zugriff auf die File-Methoden ermöglicht, die Sie jedoch verspotten können.

gbanfill
quelle