web-dev-qa-db-fra.com

Modèles de conception utiles pour les tests unitaires / TDD?

La lecture cette question m'a aidé à consolider certains des problèmes que j'ai toujours eu avec les tests unitaires, TDD, et al.

Depuis que j'ai découvert l'approche TDD du développement, je savais que c'était la bonne voie à suivre. La lecture de divers tutoriels m'a aidé à comprendre comment commencer, mais ils ont toujours été très simplistes - pas vraiment quelque chose que l'on peut appliquer à un projet actif. Le mieux que j'ai réussi est d'écrire des tests autour de petites parties de mon code - des choses comme les bibliothèques, qui sont utilisées par l'application principale mais qui ne sont en aucun cas intégrées. Bien que cela ait été utile, cela équivaut à environ 5% de la base de code. Il y a très peu de choses sur la façon de passer à l'étape suivante, pour m'aider à obtenir des tests dans l'application principale.

Des commentaires tels que " La plupart des codes sans tests unitaires sont construits avec des dépendances dures (c'est-à-dire des nouveautés partout) ou des méthodes statiques. "et" ... il n'est pas rare d'avoir un haut niveau de couplage entre les classes, des objets difficiles à configurer à l'intérieur de votre classe [...] et ainsi de suite. "m'ont fait réaliser que la prochaine étape consiste à comprendre comment découpler le code pour le rendre testable.

Que dois-je regarder pour m'aider à faire cela? Existe-t-il un ensemble spécifique de modèles de conception que je dois comprendre et commencer à mettre en œuvre, ce qui permettra des tests plus faciles?

50
Phillip B Oldham

Ici, Mike Clifton décrit 24 modèles de test de 2004. C'est une heuristique utile lors de la conception de tests unitaires.

http://www.codeproject.com/Articles/5772/Advanced-Unit-Test-Part-V-Unit-Test-Patterns

Modèles de réussite/échec

Ces modèles sont votre première ligne de défense (ou attaque, selon votre point de vue) pour garantir un bon code. Mais attention, ils sont trompeurs dans ce qu'ils vous disent sur le code.

  • Le modèle de test simple
  • Le modèle de chemin de code
  • Le modèle de plage de paramètres

Modèles de transaction de données

Les modèles de transaction de données sont un début pour embrasser les problèmes de persistance et de communication des données. Plus sur ce sujet est discuté sous "Modèles de simulation". En outre, ces modèles omettent intentionnellement les tests de résistance, par exemple, le chargement sur le serveur. Ceci sera discuté sous "Modèles de test de stress".

  • Le modèle d'E/S de données simples
  • Le modèle de données de contrainte
  • Le modèle de restauration

Modèles de gestion de collection

La plupart des applications gèrent des collections d'informations. Bien qu'il existe une variété de collections disponibles pour le programmeur, il est important de vérifier (et donc de documenter) que le code utilise la bonne collection. Cela affecte l'ordre et les contraintes.

  • Le modèle de collecte-commande
  • Le modèle d'énumération
  • Modèle de contrainte de collection
  • Le modèle d'indexation des collections

Modèles de performances

Les tests unitaires ne devraient pas seulement concerner la fonction mais aussi la forme. Avec quelle efficacité le code testé remplit-il sa fonction? À quelle vitesse? Quelle quantité de mémoire utilise-t-elle? Comprend-il efficacement l'insertion des données pour la récupération des données? Libère-t-il correctement les ressources? Ce sont toutes des choses qui relèvent des tests unitaires. En incluant des modèles de performances dans le test unitaire, l'implémenteur a un objectif à atteindre, ce qui se traduit par un meilleur code, une meilleure application et un client plus satisfait.

  • Le modèle de test de performance

Modèles de processus

Le test unitaire est destiné à tester, enfin, des unités ... les fonctions de base de l'application. On peut faire valoir que les processus de test devraient être relégués aux procédures de test d'acceptation, mais je n'accepte pas cet argument. Un processus est simplement un type d'unité différent. Les processus de test avec un testeur unitaire offrent les mêmes avantages que les autres tests unitaires - ils documentent la façon dont le processus est censé fonctionner et le testeur unitaire peut aider le réalisateur en testant également le processus dans le désordre, identifiant rapidement les problèmes d'interface utilisateur potentiels comme bien. Le terme "processus" comprend également les transitions d'états et les règles métier, qui doivent toutes deux être validées.

  • Le modèle de séquence de processus
  • Le modèle d'état du processus
  • Le modèle de règle de processus

Modèles de simulation

Les transactions de données sont difficiles à tester car elles nécessitent souvent une configuration prédéfinie, une connexion ouverte et/ou un appareil en ligne (pour n'en nommer que quelques-uns). Les objets fictifs peuvent venir à la rescousse en simulant la base de données, le service Web, l'événement utilisateur, la connexion et/ou le matériel avec lequel le code transige. Les objets fictifs ont également la capacité de créer des conditions de défaillance très difficiles à reproduire dans le monde réel - une connexion avec perte, un serveur lent, un concentrateur de réseau défaillant, etc.

  • Modèle d'objet maquette
  • Le modèle de simulation de service
  • Le modèle de simulation d'erreur sur les bits
  • Le modèle de simulation de composant

Modèles de multithreading

Le test unitaire des applications multithreads est probablement l'une des choses les plus difficiles à faire car vous devez configurer une condition qui, par sa nature même, est destinée à être asynchrone et donc non déterministe. Ce sujet est probablement un article majeur en soi, donc je ne fournirai ici qu'un modèle très générique. De plus, pour effectuer correctement de nombreux tests de thread, l'application de testeur d'unité doit elle-même exécuter des tests en tant que threads séparés afin que le testeur d'unité ne soit pas désactivé lorsqu'un thread se retrouve dans un état d'attente.

  • Le modèle signalé
  • Le modèle de résolution de l'impasse

Modèles de test de résistance

La plupart des applications sont testées dans des environnements idéaux - le programmeur utilise une machine rapide avec peu de trafic réseau, utilisant de petits ensembles de données. Le monde réel est très différent. Avant que quelque chose ne casse complètement, l'application peut subir une dégradation et répondre mal ou avec des erreurs à l'utilisateur. Les tests unitaires qui vérifient les performances du code sous contrainte doivent être rencontrés avec la même ferveur (sinon plus) que les tests unitaires dans un environnement idéal.

  • Le modèle de test de contrainte de données en masse
  • Le modèle de test de stress des ressources
  • Le modèle de test de chargement

Modèles de calques de présentation

L'un des aspects les plus difficiles des tests unitaires consiste à vérifier que les informations parviennent à l'utilisateur directement au niveau de la couche de présentation elle-même et que le fonctionnement interne de l'application définit correctement l'état de la couche de présentation. Souvent, les couches de présentation sont enchevêtrées avec des objets métier, des objets de données et une logique de contrôle. Si vous prévoyez de tester à l'unité la couche de présentation, vous devez vous rendre compte qu'une séparation nette des préoccupations est obligatoire. Une partie de la solution consiste à développer une architecture Model-View-Controller (MVC) appropriée. L'architecture MVC fournit un moyen de développer de bonnes pratiques de conception lorsque vous travaillez avec la couche de présentation. Cependant, il est facilement abusé. Un certain degré de discipline est nécessaire pour vous assurer que vous implémentez correctement l'architecture MVC, plutôt que dans Word uniquement.

  • Le modèle de test d'état d'affichage
  • Le modèle de test d'état du modèle
54
Dave

Je dirais que vous avez principalement besoin de deux choses à tester, et elles vont de pair:

  • Interfaces, interfaces, interfaces
  • injection de dépendance; ceci en conjonction avec des interfaces vous aidera à permuter les pièces à volonté pour isoler les modules que vous souhaitez tester. Vous souhaitez tester votre système cron qui envoie des notifications à d'autres services? instanciez-le et substituez votre implémentation en code réel à tout le reste par des composants obéissant à la bonne interface mais câblés pour réagir de la manière que vous souhaitez tester: notification par courrier? tester ce qui se passe lorsque le serveur smtp est en panne en lançant une exception

Je n'ai pas moi-même maîtrisé l'art des tests unitaires (et j'en suis loin), mais c'est là que mes principaux efforts vont actuellement. Le problème est que je ne conçois toujours pas pour les tests, et par conséquent mon code doit se plier en arrière pour s'adapter ...

10
samy

Le livre de Michael Feather Working Effectively With Legacy Code est exactement ce que vous cherchez. Il définit le code hérité comme "code sans tests" et explique comment le mettre à l'essai.

Comme pour la plupart des choses, c'est une étape à la fois. Lorsque vous apportez une modification ou un correctif, essayez d'augmenter la couverture du test. Au fil du temps, vous aurez un ensemble de tests plus complet. Il traite des techniques de réduction du couplage et de la manière d'adapter les éprouvettes entre la logique d'application.

Comme indiqué dans d'autres réponses, l'injection de dépendance est un bon moyen d'écrire du code testable (et généralement peu couplé).

7
Paul Rubel

Arrange, Act, Assert est un bon exemple de modèle qui vous aide à structurer votre code de test autour de cas d'utilisation particuliers.

Voici un code C # hypothétique qui illustre le modèle.

[TestFixture]
public class TestSomeUseCases() {

    // Service we want to test
    private TestableServiceImplementation service;

    // IoC-injected mock of service that's needed for TestableServiceImplementation
    private Mock<ISomeService> dependencyMock;

    public void Arrange() {
        // Create a mock of auxiliary service
        dependencyMock = new Mock<ISomeService>();
        dependencyMock.Setup(s => s.GetFirstNumber(It.IsAny<int>)).Return(1);

        // Create a tested service and inject the mock instance
        service = new TestableServiceImplementation(dependencyMock.Object);
    }

    public void Act() {
        service.ProcessFirstNumber();
    }

    [SetUp]
    public void Setup() {
        Arrange();
        Act();
    }

    [Test]
    public void Assert_That_First_Number_Was_Processed() {
        dependencyMock.Verify(d => d.GetFirstNumber(It.IsAny<int>()), Times.Exactly(1));
    }
}

Si vous avez beaucoup de scénarios à tester, vous pouvez extraire une classe abstraite commune avec des bits Arrange & Act concrets (ou simplement Arranger) et implémenter les bits abstraits restants et les fonctions de test dans les classes héritées qui regroupent les fonctions de test.

4
Art

Modèles de test xUnit de Gerard Meszaros: le code de test de refactoring regorge de modèles pour les tests unitaires. Je sais que vous cherchez des modèles sur TDD, mais je pense que vous trouverez beaucoup de matériel utile dans ce livre

Le livre est en safari, vous pouvez donc avoir un très bon aperçu de ce qu'il y a à l'intérieur pour voir si cela pourrait être utile: http://my.safaribooksonline.com/978013149505

4
Mark Irvine

m'ont fait réaliser que la prochaine étape consiste à comprendre comment découpler le code pour le rendre testable.

Que dois-je regarder pour m'aider à faire cela? Existe-t-il un ensemble spécifique de modèles de conception que je dois comprendre et commencer à mettre en œuvre, ce qui permettra des tests plus faciles?

Tout de suite! SOLID est ce que vous recherchez (oui, vraiment). Je continue de recommander ces 2 ebooks , spécialement celui sur SOLID pour le problème à portée de main.

Vous devez également comprendre que c'est très difficile si vous introduisez des tests unitaires dans une base de code existante. Malheureusement, le code étroitement couplé est beaucoup trop courant. Cela ne signifie pas de ne pas le faire, mais pendant un bon moment, ce sera comme vous l'avez mentionné, les tests seront plus concentrés en petits morceaux.

Au fil du temps, ceux-ci deviennent plus étendus, mais cela dépend de la taille de la base de code existante, de la taille de l'équipe et du nombre de personnes qui le font au lieu d'ajouter au problème.

3
eglasius

Les modèles de conception ne sont pas directement pertinents pour TDD, car ce sont des détails d'implémentation. Vous ne devriez pas essayer d'intégrer des modèles dans votre code simplement parce qu'ils existent, mais ils ont plutôt tendance à apparaître à mesure que votre code évolue. Ils deviennent également utiles si votre code sent mauvais, car ils aident à résoudre ces problèmes. Ne développez pas de code avec des modèles de conception à l'esprit, écrivez simplement du code. Faites ensuite passer les tests et refactorisez.

2
Grant Palin

De nombreux problèmes comme celui-ci peuvent être résolus avec une encapsulation appropriée. Ou, vous pourriez avoir ce problème si vous mélangez vos préoccupations. Supposons que vous ayez du code qui valide un utilisateur, valide un objet de domaine, puis enregistre l'objet de domaine dans une seule méthode ou classe. Vous avez mélangé vos préoccupations et vous n'allez pas être heureux. Vous devez séparer ces préoccupations (authentification/autorisation, logique métier, persistance) afin de pouvoir les tester isolément.

Les modèles de conception aident, mais beaucoup de ceux exotiques ont des problèmes très étroits auxquels ils peuvent être appliqués. Les modèles comme composite, command, sont souvent utilisés et sont simples.

La directive est la suivante: s'il est très difficile de tester quelque chose, vous pouvez probablement le refactoriser en problèmes plus petits et tester les bits refactorisés de manière isolée. Donc, si vous avez une méthode de 200 lignes avec 5 niveaux d'instructions if et quelques boucles for, vous voudrez peut-être casser cette ventouse.

Donc, commencez par voir si vous pouvez simplifier le code compliqué en séparant vos préoccupations, puis voyez si vous pouvez simplifier le code compliqué en le décomposant. Bien sûr, si un motif de conception vous saute aux yeux, allez-y.

1
hvgotcodes

Injection de dépendance/IoC. Lisez également les frameworks d'injection de dépendances tels que SpringFramework et google-guice. Ils visent également à écrire du code testable.

0
walters