Comment simuler le système de fichiers en C # pour les tests unitaires?

149

Existe-t-il des bibliothèques ou des méthodes pour simuler le système de fichiers en C # pour écrire des tests unitaires? Dans mon cas actuel, j'ai des méthodes qui vérifient si certains fichiers existent et lisent la date de création. J'aurai peut-être besoin de plus que cela à l'avenir.

pupeno
la source
1
Cela ressemble à un double de plusieurs autres, y compris: stackoverflow.com/questions/664277/… .
John Saunders
Essayez peut-être de chercher dans pex ( research.microsoft.com/en-us/projects/pex/filesystem.pdf )
Tinus
2
@Mitch: La plupart du temps, il suffit de placer les données dans le système de fichiers et de laisser les tests unitaires suivre leur cours. Cependant, j'ai rencontré des méthodes qui exécutent de nombreuses opérations d'E / S, et la configuration de l'environnement de test pour ces méthodes est grandement simplifiée en utilisant un système de fichiers simulé.
Steve Guidi
J'ai écrit github.com/guillaume86/VirtualPath dans ce but (et plus), c'est toujours WIP et l'API va certainement changer mais cela fonctionne déjà, et certains tests sont inclus.
Guillaume86

Réponses:

154

Edit: installez le package NuGet System.IO.Abstractions.

Ce package n'existait pas lorsque cette réponse a été acceptée à l'origine. La réponse originale est fournie pour le contexte historique ci-dessous:

Vous pouvez le faire en créant une interface:

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

et la création d'une implémentation «réelle» qui utilise System.IO.File.Exists () etc. Vous pouvez ensuite vous moquer de cette interface en utilisant un cadre de simulation; Je recommande Moq .

Edit: quelqu'un l'a fait et l'a gentiment mis en ligne ici .

J'ai utilisé cette approche pour simuler DateTime.UtcNow dans une interface IClock (vraiment très utile pour que nos tests puissent contrôler le flux du temps!), Et plus traditionnellement, une interface ISqlDataAccess.

Une autre approche pourrait être d'utiliser TypeMock , cela vous permet d'intercepter les appels aux classes et de les stuber. Cependant, cela coûte de l'argent et devrait être installé sur les PC de toute votre équipe et sur votre serveur de construction afin de fonctionner.De plus, cela ne fonctionnera apparemment pas pour le fichier System.IO.File, car il ne peut pas stuber mscorlib .

Vous pouvez également accepter simplement que certaines méthodes ne soient pas testables unitaire et les tester dans une suite de tests système / intégration lente distincte.

Matt Howells
la source
1
À mon avis, créer une interface comme Matt le décrit ici est la voie à suivre. J'ai même écrit un outil qui génère de telles interfaces pour vous, ce qui est utile lorsque vous essayez de simuler des classes statiques et / ou scellées, ou des méthodes non déterministes (c'est-à-dire des horloges et des générateurs de nombres aléatoires). Voir jolt.codeplex.com pour plus d'informations.
Steve Guidi
Il semble que le dépôt dans l'article référencé a été supprimé / déplacé sans préavis. Cependant, il semble y avoir un paquet nuget de ses efforts ici: nuget.org/packages/mscorlib-mock
Mike-E
Typemock a des restrictions sur les types qui peuvent être falsifiés, mais (au moins dans la version actuelle d'octobre 2017) vous pouvez certainement simuler la classe statique File. Je viens de vérifier cela moi-même.
Ryan Rodemoyer
Pouvez-vous résumer quelques suites de tests d'intégration?
Ozkan
83

Install-Package System.IO.Abstractions

Cette bibliothèque imaginaire existe maintenant, il existe un package NuGet pour System.IO.Abstractions , qui fait abstraction de l'espace de noms System.IO.

Il existe également un ensemble d'aide au test, System.IO.Abstractions.TestingHelpers qui - au moment de la rédaction - n'est que partiellement implémenté, mais constitue un très bon point de départ.

Binaire Worrier
la source
3
Je pense que standardiser autour de cette abstraction déjà construite est le meilleur pari. Jamais entendu parler de cette bibliothèque, alors merci beaucoup pour la mise en garde.
julealgon
PM signifie package manager .. pour ouvrir ... Outils> NuGet Package Manager> Package Manager Console
thedanotto
11

Vous allez probablement devoir créer un contrat pour définir les éléments dont vous avez besoin dans le système de fichiers, puis écrire un wrapper autour de ces fonctionnalités. À ce stade, vous pourrez vous moquer ou supprimer la mise en œuvre.

Exemple:

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
la source
5

Ma recommandation est d'utiliser http://systemwrapper.codeplex.com/ car il fournit des wrappers pour les types les plus utilisés dans l'espace de noms System

adeel41
la source
J'utilise actuellement cette bibliothèque, et maintenant que j'ai découvert que ses abstractions pour des choses comme FileStream n'incluent pas IDisposable, je cherche un remplacement. Si la bibliothèque ne me permet pas de disposer correctement des flux, je ne peux pas la recommander (ou l'utiliser) pour gérer ce type d'opérations.
James Nail
1
IFileStreamWrap de SystemWrapper implémente désormais IDisposable.
tster
systemwrapper est uniquement un framework .net, il causera des problèmes étranges s'il est utilisé avec .netcore
Adil H. Raza
3

J'ai rencontré les solutions suivantes à cela:

  • Écrivez des tests d'intégration, pas des tests unitaires. Pour que cela fonctionne, vous avez besoin d'un moyen simple de créer un dossier dans lequel vous pouvez vider des éléments sans vous soucier des interférences d'autres tests. J'ai une classe TestFolder simple qui peut créer un dossier unique par méthode de test à utiliser.
  • Écrivez un System.IO.File mockable. C'est créer un IFile.cs . Je trouve que l'utilisation de cela se termine souvent par des tests qui prouvent simplement que vous pouvez écrire des déclarations moqueuses, mais que vous l'utilisez lorsque l'utilisation d'E / S est faible.
  • Examinez votre couche d'abstraction et extrayez le fichier IO de la classe. Le créer une interface pour cela. Les autres utilisent des tests d'intégration (mais ce sera très petit). Cela diffère de ce qui précède au lieu de faire un fichier.Lisez vous écrivez l'intention, dites ioThingie.loadSettings ()
  • System.IO.Abstractions . Je ne l'ai pas encore utilisé, mais c'est celui avec lequel je suis le plus excité de jouer.

Je finis par utiliser toutes les méthodes ci-dessus, en fonction de ce que j'écris. Mais la plupart du temps, je finis par penser que l'abstraction est fausse lorsque j'écris des tests unitaires qui touchent l'IO.

Michael Lloyd Lee mlk
la source
4
Le lien vers IFile.cs est rompu.
Mike-E
3

En utilisant System.IO.Abstractions et System.IO.Abstractions.TestingHelpers comme ça:

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

Dans votre classe de test, vous utilisez MockFileSystem () pour simuler un fichier et vous instanciez ManageFile comme:

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

Vous pouvez le faire en utilisant Microsoft Fakes sans avoir besoin de modifier votre base de code, par exemple, car elle était déjà gelée.

Générez d' abord un faux assembly pour System.dll - ou tout autre package, puis simulez les retours attendus comme dans:

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
la source
1

Il serait difficile de se moquer du système de fichiers dans un test car les API de fichier .NET ne sont pas vraiment basées sur des interfaces ou des classes extensibles qui pourraient être simulées.

Cependant, si vous disposez de votre propre couche fonctionnelle pour accéder au système de fichiers, vous pouvez vous en moquer lors d'un test unitaire.

Au lieu de vous moquer, envisagez simplement de créer les dossiers et fichiers dont vous avez besoin dans le cadre de votre configuration de test et de les supprimer dans votre méthode de démontage.

LBushkin
la source
1

Je ne sais pas comment vous feriez une maquette du système de fichiers. Ce que vous pouvez faire est d'écrire une configuration de montage de test qui crée un dossier, etc. avec la structure nécessaire pour les tests. Une méthode de démontage le nettoierait après l'exécution des tests.

Édité pour ajouter: En y réfléchissant un peu plus, je ne pense pas que vous vouliez vous moquer du système de fichiers pour tester ce type de méthodes. Si vous vous moquez du système de fichiers pour renvoyer true si un certain fichier existe et que vous l'utilisez dans votre test d'une méthode qui vérifie si ce fichier existe, alors vous ne testez pas grand-chose. Il serait utile de se moquer du système de fichiers si vous vouliez tester une méthode qui dépendait du système de fichiers mais que l'activité du système de fichiers ne faisait pas partie intégrante de la méthode testée.

Jamie Ide
la source
1

Pour répondre à votre question spécifique: Non, il n'y a pas de bibliothèques qui vous permettront de simuler des appels d'E / S de fichiers (que je connaisse). Cela signifie que le test unitaire "correctement" de vos types exigera que vous preniez en compte cette restriction lorsque vous définissez vos types.

Note d'accompagnement rapide sur la façon dont je définis un test unitaire «approprié». Je pense que les tests unitaires devraient confirmer que vous obtenez le résultat attendu (qu'il s'agisse d'une exception, d'un appel à une méthode, etc.) à condition que les entrées connues soient fournies. Cela vous permet de configurer vos conditions de test unitaire comme un ensemble d'entrées et / ou d'états d'entrée. Le meilleur moyen que j'ai trouvé pour ce faire est d'utiliser des services basés sur l'interface et l'injection de dépendances afin que chaque responsabilité externe à un type soit fournie via une interface transmise via un constructeur ou une propriété.

Alors, dans cet esprit, revenons à votre question. Je me suis moqué des appels de système de fichiers en créant une IFileSystemServiceinterface avec une FileSystemServiceimplémentation qui n'est qu'une façade sur les méthodes du système de fichiers mscorlib. Mon code utilise alors IFileSystemServiceles types plutôt que les types mscorlib. Cela me permet de brancher mon standard FileSystemServicelorsque l'application est en cours d'exécution ou de simuler le IFileSystemServicedans mes tests unitaires. Le code de l'application est le même quelle que soit la façon dont il est exécuté, mais l'infrastructure sous-jacente permet à ce code d'être facilement testé.

Je reconnais qu'il est difficile d'utiliser le wrapper autour des objets du système de fichiers mscorlib mais, dans ces scénarios spécifiques, cela vaut le travail supplémentaire car les tests deviennent tellement plus faciles et plus fiables.

akmad
la source
1

Créer une interface et s'en moquer pour la tester est la manière la plus propre de procéder. Cependant, comme alternative, vous pouvez jeter un œil au framework Microsoft Moles .

Konamiman
la source
0

La solution courante consiste à utiliser une API abstraite de système de fichiers (comme Apache Commons VFS pour Java): toute la logique d'application utilise l'API et le test unitaire est capable de simuler un système de fichiers réel avec une implémentation de stub (émulation en mémoire ou quelque chose comme ça).

Pour C #, l'API similaire existe: NI.Vfs qui est très similaire à Apache VFS V1. Il contient des implémentations par défaut pour le système de fichiers local et le système de fichiers en mémoire (le dernier peut être utilisé dans les tests unitaires à partir de la boîte).

Vitaliy Fedorchenko
la source
-1

Nous utilisons actuellement un moteur de données propriétaire et son API n'est pas exposée en tant qu'interfaces, de sorte que nous pouvons difficilement tester notre code d'accès aux données. Ensuite, je suis allé avec l'approche de Matt et Joseph aussi.

Tien Do
la source
-2

J'irais avec la réponse de Jamie Ide. N'essayez pas de vous moquer des choses que vous n'avez pas écrites. Il y aura toutes sortes de dépendances que vous ne connaissiez pas - classes scellées, méthodes non virtuelles, etc.

Une autre approche consisterait à envelopper les méthodes appropriées avec quelque chose de moquable. Par exemple, créez une classe appelée FileWrapper qui permet d'accéder aux méthodes File mais que vous pouvez simuler.

gbanfill
la source