c# - testserver - webapplicationfactory




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

J'irais avec un dossier de test unique. Pour différents cas de test, vous pouvez placer différents fichiers valides / invalides dans ce dossier dans le cadre de la configuration du contexte. Dans le test de démontage, 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 en copiant / ne copiant pas le fichier approprié dans votre dossier. Vous pouvez également utiliser la table pour définir quel fichier doit être copié dans le dossier:

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

J'ai quelques classes qui implémente une certaine 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 un certain dossier a une certaine structure (par exemple, il contient des sous-dossiers avec des noms spécifiques etc ...)
  • chargement de certains fichiers à partir de ces dossiers et vérification de leur structure (par exemple, ce sont des fichiers de configuration, situés à certains endroits dans certains dossiers)
  • charger des fichiers supplémentaires pour le test / la 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 d'autres structures internes 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 le MEF (Managed Extensibility Framework) impliqué dans cette logique, car certains de ces fichiers que je vérifie sont des DLL managées que je charge manuellement sur des agrégats MEF etc ...

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

  • dossier avec la structure correcte et tous les fichiers étant valides
  • dossier avec la 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 suis pas sûr cependant comment exécuter exactement mon code dans ce scénario ... Je ne veux certainement pas courir l'application entière et le pointer pour vérifier ces dossiers moqués. Dois-je utiliser un cadre de test unitaire pour écrire des "tests unitaires", qui exécute mon code par rapport à ces objets du système de fichiers?

En général, est-ce une approche correcte pour ce genre de scénarios de test? Y a-t-il d'autres meilleures approches?


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

Essayez d'énumérer toutes les limites du domaine du problème. S'il y en a trop, réfléchissez à la possibilité que votre problème soit défini de manière trop large et qu'il doive être décomposé. Quel est l'ensemble des conditions nécessaires et suffisantes pour que votre système réussisse tous les tests? Puis 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, d'enfreindre cela. Essayez de vous prouver que vous les avez tous trouvés. Ensuite, écrivez un test pour chacun.

Je voudrais d'abord passer par le processus ci-dessus pour l'environnement, construire et tester d'abord à un niveau satisfaisant, puis pour la logique plus détaillée dans le flux de travail. Certaines itérations peuvent être nécessaires si des dépendances entre l'environnement et la logique détaillée vous apparaissent lors des tests.


Vous devriez 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 certain moment cependant, vous devrez tester les implémentations qui fonctionnent 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 des «règles officielles» pour la meilleure façon de faire des tests d'intégration de ce type, mais je crois que vous êtes sur la bonne voie. Quelques idées que vous devriez vous efforcer de:

  • Normes claires: Définissez clairement les règles et le but de chaque test.
  • Automatisation: Possibilité de réexécuter les tests rapidement et sans trop de modifications manuelles.
  • Répétabilité: Une situation de test que vous pouvez "réinitialiser", de sorte que vous pouvez 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 voudrais mettre en place deux dossiers principaux: Un dans lequel tout est comme il est censé être (c'est-à-dire fonctionne correctement), et celui dans lequel toutes les règles sont brisées.

Je voudrais créer ces dossiers et tous les fichiers en eux, puis zip 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 facilement et rapidement vos dossiers et fichiers, même si vos principaux tests d'intégration doivent être modifiés ou gâchés lors des tests. La raison de les mettre dans une classe de test est simplement de rendre alors facile à exécuter à partir de la même interface que vous travaillerez pendant les tests.

Essai

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

On ne sait pas à quel point vous êtes familiarisé avec les tests d'unité / d'intégration. En tout cas, je recommanderais NUnit . J'aime utiliser les extensions de Should aussi. 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, j'ai seulement eu une expérience réelle avec le coureur intégré dans Resharper, mais j'en suis assez satisfait et je n'ai aucun problème à le recommander.

Voici un exemple de 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 explicitement rien dans la seconde. En effet, il est marqué avec [ExpectedException], ce qui signifie qu'il échouera si une exception du type spécifié n'est pas lancée lors de l'exécution du test. Vous pouvez l'utiliser pour vérifier qu'une exception appropriée est levée lorsque l'une de vos règles est rompue.

[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(); 
    }
}

Une fois tout cela en place, vous devriez être capable de créer et de recréer des scénarios de test, et d'exécuter des tests de manière simple et répétable.

Edit: Comme indiqué dans un commentaire, Assert.Throws () est une autre option pour s'assurer que les exceptions sont levées comme requis. Personnellement, j'aime la variante de tag, et avec les paramètres , vous pouvez aussi vérifier des choses comme le message d'erreur. Un autre exemple (en supposant qu'un message d'erreur personnalisé est lancé à partir de votre calculatrice):

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

Tout d'abord , je pense, il est préférable d'écrire des tests unitaires pour tester votre logique sans toucher à des ressources externes . Ici vous avez deux options:

  1. vous devez utiliser une couche d'abstraction pour isoler votre logique des dépendances externes telles que le système de fichiers. Vous pouvez facilement bomber ou se moquer (à la main ou à l'aide de frameworks d'isolation contraints tels que NSubstitute, FakeItEasy ou Moq) de ces abstractions dans des tests unitaires. Je préfère cette option, car dans ce cas les tests vous poussent à un meilleur design.
  2. Si vous devez gérer du code hérité (uniquement dans ce cas), vous pouvez utiliser l'un des frameworks d'isolation non contraints (tels que TypeMock Isolator, JustMock ou Microsoft Fakes) qui peuvent se contenter / se moquer de 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 des tests d'intégration , alors vous devez écrire un test de "chemin heureux" (quand tout est OK) et quelques tests qui testent votre logique dans les cas limites (fichier ou répertoire non trouvé). Contrairement à @Sergey Berezovskiy, je recommande de créer des 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 dossier, vous pouvez le modifier plus facilement, car vous aurez déjà du code et des tests de travail (le refactoring sous harnais de test est beaucoup plus facile).

Pour les tests unitaires et d'intégration, vous pouvez utiliser des structures de test 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 build.

Si vous décidez d'écrire les deux types de tests, vous devez séparer les tests unitaires des tests d'intégration (vous pouvez créer des projets séparés pour chaque type de test). 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 du système après les derniers changements de code (corrections de bugs, nouvelles fonctionnalités). Si elles sont exécutées fréquemment, le développeur peut rapidement et facilement identifier le morceau de code qui a brisé le système. Personne ne veut exécuter des tests unitaires lents.
  2. les 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.




integration-testing