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:
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:
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?
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:
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:
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:
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:
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.
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(){
...
}
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
Je ne connais pas l'architecture de votre programme pour vous donner de bons conseils, mais je vais essayer
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.