web-dev-qa-db-fra.com

Comment faire des tests d'intégration dans .NET avec de vrais fichiers?

J'ai quelques classes qui implémentent une logique liée au système de fichiers et aux fichiers. Par exemple, j'effectue les tâches suivantes dans le cadre de cette logique:

  • vérifier si certains dossiers ont une certaine structure (par exemple, il contient des sous-dossiers avec des noms spécifiques, etc.)
  • charger certains fichiers à partir de ces dossiers et vérifier leur structure (par exemple, ce sont des fichiers de configuration, situés à un certain endroit dans un certain dossier)
  • charger des fichiers supplémentaires pour les tests/validation à partir du fichier de configuration (par exemple, ce fichier de configuration contient des informations sur d'autres fichiers dans le même dossier, qui devraient avoir une autre structure interne, etc.)

Maintenant, toute cette logique a un flux de travail et des exceptions sont levées, si quelque chose ne va pas (par exemple, le fichier de configuration n'est pas trouvé à l'emplacement du dossier spécifique). De plus, il y a Managed Extensibility Framework (MEF) impliqué dans cette logique, car certains de ces fichiers que je vérifie sont des DLL gérées que je charge manuellement dans les agrégats MEF, etc.

Maintenant, j'aimerais tester tout cela d'une manière ou d'une autre. Je pensais créer plusieurs dossiers de test physiques sur le disque dur, qui couvrent divers cas de test, puis exécuter mon code contre eux. Je pourrais créer par exemple:

  • dossier avec une structure correcte et tous les fichiers étant valides
  • dossier avec une structure correcte mais avec un fichier de configuration invalide
  • dossier avec structure correcte mais fichier de configuration manquant etc ...

Serait-ce la bonne approche? Je ne sais pas exactement comment exécuter mon code dans ce scénario ... Je ne veux certainement pas exécuter toute l'application et la pointer pour vérifier ces dossiers simulés. Dois-je utiliser un cadre de tests unitaires pour écrire des "tests unitaires" qui exécutent mon code sur ces objets du système de fichiers?

En général, tout cela est-il une approche correcte pour ce type de scénarios de test? Existe-t-il d'autres meilleures approches?

54
matori82

Tout d'abord, je pense que c'est mieux vaut écrire des tests unitaires pour tester votre logique sans toucher à aucune ressource externe. Ici, vous avez deux options:

  1. vous devez utiliser la couche d'abstraction pour isoler votre logique des dépendances externes telles que le système de fichiers. Vous pouvez facilement tronquer ou simuler (à la main ou à l'aide d'un cadre d'isolement contraint tel que NSubstitute, FakeItEasy ou Moq) ces abstractions dans des tests unitaires. Je préfère cette option, car dans ce cas les tests vous poussent vers un meilleur design.
  2. si vous devez gérer du code hérité (uniquement dans ce cas), vous pouvez utiliser l'un des cadres d'isolation non contraints (tels que TypeMock Isolator, JustMock ou Microsoft Fakes) qui peuvent bloquer/simuler à peu près tout (par exemple, scellé et statique classes, méthodes non virtuelles). Mais ils coûtent de l'argent. La seule option "gratuite" est Microsoft Fakes, sauf si vous êtes l'heureux propriétaire de Visual Studio 2012/2013 Premium/Ultimate.

Dans les tests unitaires, vous n'avez pas besoin de tester la logique des bibliothèques externes telles que MEF.

Deuxièmement, si vous voulez écrire tests d'intégration, alors vous devez écrire le test "happy path" (quand tout est OK) et quelques tests qui testent votre logique dans les cas limites (Fichier ou répertoire introuvables). Contrairement à @Sergey Berezovskiy, je recommande de créer dossiers séparés pour chaque cas de test. Les principaux avantages sont:

  1. vous pouvez donner à votre dossier des noms significatifs qui expriment plus clairement vos intentions;
  2. vous n'avez pas besoin d'écrire une logique de configuration/démontage complexe (c'est-à-dire fragile).
  3. même si vous décidez plus tard d'utiliser une autre structure de dossiers, vous pouvez la changer plus facilement, car vous aurez déjà du code et des tests en état de marche (la refactorisation sous le faisceau de tests est beaucoup plus facile).

Pour les tests unitaires et d'intégration, vous pouvez utiliser des frameworks de tests unitaires ordinaires (comme NUnit ou xUnit.NET). Avec ces frameworks, il est assez facile de lancer vos tests dans des scénarios d'intégration continue sur votre serveur de génération.

Si vous décidez d'écrire les deux types de tests, alors vous devez séparer les tests unitaires des tests d'intégration (vous pouvez créer des projets distincts pour chaque type de tests). Raisons pour cela:

  1. tests unitaires est un filet de sécurité pour les développeurs. Ils doivent fournir un retour rapide sur le comportement attendu des unités système après les derniers changements de code (corrections de bugs, nouvelles fonctionnalités). S'ils sont exécutés fréquemment, le développeur peut identifier rapidement et facilement un morceau de code qui a cassé le système. Personne ne veut exécuter des tests unitaires lents.
  2. tests d'intégration sont généralement plus lents que les tests unitaires. Mais ils ont un but différent. Ils vérifient que les unités fonctionnent comme prévu avec des dépendances réelles.
59
Vladimir Almaev

Vous devez tester autant de logique que possible avec des tests unitaires, en faisant abstraction des appels au système de fichiers derrière les interfaces. L'utilisation de l'injection de dépendances et d'un framework de test tel que FakeItEasy vous permettra de tester que vos interfaces sont réellement utilisées/appelées pour fonctionner sur les fichiers et les dossiers.

À un moment donné cependant, vous devrez également tester les implémentations fonctionnant sur le système de fichiers, et c'est là que vous aurez besoin de tests d'intégration.

Les choses que vous devez tester semblent être relativement isolées puisque tout ce que vous voulez tester est vos propres fichiers et répertoires, sur votre propre système de fichiers. Si vous vouliez tester une base de données ou un autre système externe avec plusieurs utilisateurs, etc., les choses pourraient être plus compliquées.

Je ne pense pas que vous trouverez de "règles officielles" sur la meilleure façon de faire des tests d'intégration de ce type, mais je pense que vous êtes sur la bonne voie. Quelques idées vers lesquelles vous devriez tendre:

  • Normes claires: Rendre les règles et le but de chaque test absolument clairs.
  • Automatisation: La possibilité de relancer les tests rapidement et sans trop de réglages manuels.
  • Répétabilité: Une situation de test que vous pouvez "réinitialiser", afin que vous puissiez réexécuter les tests rapidement, avec seulement de légères variations.

Créer un scénario de test reproductible

Dans votre situation, je créerais deux dossiers principaux: un dans lequel tout est comme il est censé être (c'est-à-dire fonctionnant correctement), et un dans lequel toutes les règles sont enfreintes.

Je voudrais créer ces dossiers et tous les fichiers qu'ils contiennent, puis compresser chacun des dossiers et écrire la logique dans une classe de test pour décompresser chacun d'eux.

Ce ne sont pas vraiment des tests; Considérez-les plutôt comme des "scripts" pour configurer votre scénario de test, vous permettant de supprimer et de recréer vos dossiers et fichiers facilement et rapidement, même si vos principaux tests d'intégration doivent être modifiés ou gâchés pendant les tests. La raison de les mettre dans une classe de test est simplement de les rendre faciles à exécuter à partir de la même interface que celle avec laquelle vous travaillerez pendant les tests.

Essai

Créez deux ensembles de classes de test, un ensemble pour chaque situation (configurer correctement le dossier par rapport au dossier avec des règles brisées). Placez ces tests dans une hiérarchie de dossiers qui vous semble significative (en fonction de la complexité de votre situation).

Vous ne savez pas dans quelle mesure vous connaissez les tests unitaires/d'intégration. Dans tous les cas, je recommanderais NUnit . J'aime aussi utiliser les extensions dans Should. Vous pouvez obtenir les deux de Nuget:

install-package Nunit
install-package Should

Le paquet devrait vous permettre d'écrire le code de test de la manière suivante:

someCalculatedIntValue.ShouldEqual(3); 
someFoundBoolValue.ShouldBeTrue();

Notez qu'il existe plusieurs testeurs disponibles pour exécuter vos tests. Personnellement, je n'ai eu aucune expérience réelle avec le coureur intégré à Resharper, mais j'en suis assez satisfait et je n'ai aucun problème à le recommander.

Voici un exemple d'une classe de test simple avec deux tests. Notez que dans le premier, nous vérifions une valeur attendue en utilisant une méthode d'extension de Should, alors que nous ne testons rien de manière explicite dans le second. En effet, il est balisé avec [ExpectedException], ce qui signifie qu'il échouera si une exception du type spécifié n'est pas levée lors de l'exécution du test. Vous pouvez l'utiliser pour vérifier qu'une exception appropriée est levée chaque fois qu'une de vos règles est violée.

[TestFixture] 
public class When_calculating_sums
{                    
    private MyCalculator _calc;
    private int _result;

    [SetUp] // Runs before each test
    public void SetUp() 
    {
        // Create an instance of the class to test:
        _calc = new MyCalculator();

        // Logic to test the result of:
        _result = _calc.Add(1, 1);
    }

    [Test] // First test
    public void Should_return_correct_sum() 
    {
        _result.ShouldEqual(2);
    }

    [Test] // Second test
    [ExpectedException(typeof (DivideByZeroException))]
    public void Should_throw_exception_for_invalid_values() 
    {
        // Divide by 0 should throw a DivideByZeroException:
        var otherResult = _calc.Divide(5, 0);
    }       

    [TearDown] // Runs after each test (seldom needed in practice)
    public void TearDown() 
    {
        _calc.Dispose(); 
    }
}

Avec tout cela en place, vous devriez pouvoir créer et recréer des scénarios de test, et exécuter des tests sur eux de manière simple et reproductible.


Modifier: Comme indiqué dans un commentaire, Assert.Throws () est une autre option pour garantir que les exceptions sont levées selon les besoins. Personnellement, j'aime bien la variante de balise, et avec paramètres , vous pouvez également vérifier des choses comme le message d'erreur. Un autre exemple (en supposant qu'un message d'erreur personnalisé est lancé depuis votre calculatrice):

[ExpectedException(typeof(DivideByZeroException), ExpectedMessage="Attempted to divide by zero" )]
public void When_attempting_something_silly(){  
    ...
}
8
Kjartan

J'irais avec un seul dossier de test. Pour divers cas de test, vous pouvez placer différents fichiers valides/non valides dans ce dossier dans le cadre de la configuration du contexte. Dans le démontage de test, supprimez simplement ces fichiers du dossier.

Par exemple. avec Specflow :

Given configuration file not exist
When something
Then foo

Given configuration file exists
And some dll not exists
When something
Then bar

Définissez chaque étape de configuration du contexte comme copiant/ne copiant pas le fichier approprié dans votre dossier. Vous pouvez également utiliser table pour définir le fichier à copier dans le dossier:

Given some scenario
| FileName         |
| a.config         |
| b.invalid.config |
When something
Then foobar
3
Sergey Berezovskiy

Je ne connais pas l'architecture de votre programme pour vous donner de bons conseils, mais je vais essayer

  1. Je crois que vous n'avez pas besoin de tester la structure réelle du fichier . Les services d'accès aux fichiers sont définis par le système/la structure et n'ont pas besoin d'être testés. Vous devez vous moquer de ces services dans des tests associés.
  2. De plus, vous n'avez pas besoin de tester MEF. Il est déjà testé.
  3. Utilisez principes SOLID pour effectuer des tests unitaires. Regardez en particulier principe de responsabilité unique cela vous permettra de créer des tests unitaires, qui ne seront pas liés à chacun autres. N'oubliez pas de vous moquer pour éviter les dépendances.
  4. Pour effectuer des tests d'intégration, vous pouvez créer un ensemble de classes d'assistance, qui émuleront des scénarios de structures de fichiers que vous souhaitez tester. Cela vous permettra de ne pas être attaché à la machine sur laquelle vous exécuterez ces tests. Une telle approche est peut-être plus compliquée que la création d'une véritable structure de fichiers, mais j'aime ça.
1
Max

Je construirais la logique du framework et testerais les problèmes de concurrence et les exceptions du système de fichiers pour assurer un environnement de test bien défini.

Essayez de répertorier toutes les limites du domaine problématique. S'il y en a trop, envisagez la possibilité que votre problème soit trop largement défini et doive être décomposé. Quel est l'ensemble complet des conditions nécessaires et suffisantes pour que votre système réussisse tous les tests? Ensuite, examinez chaque condition et traitez-la comme un point d'attaque individuel. Et dressez la liste de toutes les façons dont vous pouvez penser, de violer cela. Essayez de vous prouver que vous les avez tous trouvés. Ensuite, écrivez un test pour chacun.

Je passerais d'abord par le processus ci-dessus pour l'environnement, je le construirais et le testerais d'abord à un niveau satisfaisant, puis pour la logique plus détaillée du flux de travail. Une certaine itération peut être requise si des dépendances entre l'environnement et la logique détaillée vous viennent à l'esprit lors des tests.

0
Brad Thomas