web-dev-qa-db-fra.com

Comment simulez-vous le système de fichiers en C # pour les tests unitaires?

Existe-t-il des bibliothèques ou des méthodes permettant de 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 un fichier existe et qui lisent la date de création. J'aurai peut-être besoin de plus que cela à l'avenir.

136
pupeno

Edit: Installez le paquet NuGet System.IO.Abstractions .

Ce paquet n'existait pas quand 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 créer une implémentation "réelle" qui utilise System.IO.File.Exists (), etc. Vous pouvez ensuite vous moquer de cette interface en utilisant un framework moqueur; Je recommande Moq .

Edit: Quelqu'un a fait cela et l'a gentiment posté 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 temps!) Et, plus traditionnellement, une interface ISqlDataAccess.

Une autre approche pourrait consister à utiliser TypeMock , ce qui vous permet d'intercepter les appels de classes et de les remplacer. Cela coûte toutefois de l’argent et il faudrait l’installer sur les ordinateurs de votre équipe et sur votre serveur de build pour pouvoir fonctionner. De plus, cela ne fonctionnera apparemment pas pour System.IO.File, car peut ' t stub mscorlib .

Vous pouvez également simplement accepter que certaines méthodes ne sont pas testables par unité et les tester dans une suite distincte de tests d'intégration et de système, dont l'exécution est lente.

141
Matt Howells

Install-Package System.IO.Abstractions

Cette bibliothèque imaginaire existe maintenant, il existe un paquet NuGet pour System.IO.Abstractions , qui supprime l’espace de noms System.IO.

Il existe également un ensemble d’aides au test, System.IO.Abstractions.TestingHelpers qui - au moment de la rédaction du présent document - n’était que partiellement implémenté, mais constituait un très bon point de départ.

77
Binary Worrier

Vous devrez probablement créer un contrat pour définir les éléments dont vous avez besoin dans le système de fichiers, puis créer un wrapper autour de ces fonctionnalités. À ce stade, vous pourrez vous moquer de 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"); }
}
10
Joseph

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.

5
adeel41

J'ai trouvé les solutions suivantes à cela:

  • Écrire 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 transférer des éléments sans vous soucier des interférences des autres tests. J'ai une simple classe TestFolder qui peut créer un dossier de méthode de test unique à utiliser.
  • Ecrivez un System.IO.File mockable. C'est créer un IFile.cs . Je trouve que l’utilisation de cette méthode aboutit souvent à des tests qui prouvent simplement que vous pouvez écrire des déclarations moqueuses, mais ne les utilisez que lorsque l’utilisation IO est faible.
  • Examinez votre couche d’abstraction et extrayez le fichier IO de la classe. Créez une interface pour cela. Les autres utilisent des tests d’intégration (mais ce sera très petit). au lieu de faire file.Read 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é à l'idée 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 une erreur lorsque j’écris des tests unitaires qui atteignent l’IO.

3

Créer une interface et se moquer de lui pour les tests est la solution la plus propre. Cependant, vous pouvez également consulter le cadre Microsoft Moles .

1
Konamiman

Pour répondre à votre question spécifique: Non, aucune bibliothèque ne vous permettra de simuler des appels d'E/S sur des fichiers (à ma connaissance). Cela signifie que pour "tester" correctement vos types d'unités, vous devrez prendre en compte cette restriction lorsque vous définissez vos types.

Petite note sur la façon dont je définis un "bon" test unitaire. Je pense que les tests unitaires doivent confirmer que vous obtenez le résultat attendu (exception, appel d'une méthode, etc.) fourni. Cela vous permet de configurer vos conditions de test d'unité en tant qu'ensemble d'entrées et/ou d'états d'entrée. La meilleure façon de procéder consiste à utiliser des services basés sur une interface et une injection de dépendance, de sorte que chaque responsabilité externe à un type soit fournie via une interface transmise via un constructeur ou une propriété.

Donc, gardant cela à l’esprit, revenons à votre question. J'ai simulé des appels de système de fichiers en créant une interface IFileSystemService ainsi qu'une implémentation de FileSystemService qui constitue simplement une façade sur les méthodes du système de fichiers mscorlib. Mon code utilise ensuite les types IFileSystemService plutôt que les types mscorlib. Cela me permet de brancher mon FileSystemService standard lorsque l'application est en cours d'exécution ou de simuler le IFileSystemService dans mes tests unitaires. Le code de l'application est identique quelle que soit la manière dont il est exécuté, mais l'infrastructure sous-jacente permet de le tester facilement.

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

1
akmad

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

Tout d'abord générer un faux Assembly pour System.dll - ou tout autre paquet, puis simuler les retours attendus comme suit:

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

En utilisant System.IO.Abstractions 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 instanciez ManageFile comme suit:

var mockFileSysteme = new MockFileSystem();
var mockFileData = new MockFileData("File content");
mockFileSysteme.AddFile(mockFilePath, mockFileData );
var manageFile = new ManageFile(mockFileSysteme);
1

Je ne sais pas comment vous feriez pour simuler le système de fichiers. Ce que vous pourriez 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 nettoie après l'exécution des tests.

Edité pour ajouter: En y réfléchissant un peu plus, je ne pense pas que vous souhaitiez vous moquer du système de fichiers pour tester ce type de méthodes. Si vous moquez le système de fichiers pour qu'il renvoie true s'il existe un certain fichier et que vous l'utilisiez pour tester une méthode qui vérifie si ce fichier existe, vous ne testez pas grand-chose. Il peut être utile de se moquer du système de fichiers si vous souhaitez tester une méthode qui dépend du système de fichiers mais que l'activité du système de fichiers ne fait pas partie intégrante de la méthode testée.

1
Jamie Ide

Il serait difficile de simuler le système de fichiers lors d'un test car les API de fichiers .NET ne sont pas vraiment basées sur des interfaces ou des classes extensibles pouvant être simulées.

Toutefois, si vous avez 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 les fichiers dont vous avez besoin dans le cadre de votre configuration de test, puis de les supprimer dans votre méthode de démontage.

1
LBushkin

La solution courante consiste à utiliser une API de système de fichiers abstraite (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 l'implémentation de stub (émulation en mémoire ou quelque chose comme ça).

Pour C #, une 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).

0