Rubriques

Introduction Haut de la page

Les conceptions de test sont réalisées à l'aide d'informations provenant d'une variété d'artefacts, y compris des artefacts de conception comme les réalisations de cas d'utilisation, les modèles de conception ou les interfaces classificatrices. Les tests sont exécutés après la création des composants. Il est courant de créer les conceptions de test juste avant l'exécution prévue des test, bien après la création des artefacts de conception du logiciel. La figure 1 ci-après montre un exemple. Ici, la conception du test commence vers la fin de l'implémentation. Elle s'inspire des résultats de la conception des composants. La flèche allant d'Implémentation à Exécution du test indique que les tests ne peuvent pas être exécutés tant que l'implémentation n'est pas terminée.

Diagramme de l'emplacement de la conception de test dans le cycle de vie

Fig1 : la conception du test intervient traditionnellement plus tard dans le cycle de vie

Toutefois, ceci n'est pas obligatoire. Bien que l'exécution du test doive attendre que le composant ait été implémenté, la conception du test peut être réalisée plus tôt. Elle peut être réalisée juste après que l'artefact de conception est terminé. Elle peut même être réalisée en parallèle avec la conception du composant, comme indiqué ici :

Diagramme de la Conception pilotée par le test

Fig2 : la Conception pilotée par le test aligne chronologiquement la conception du test avec la conception du logiciel

Déplacer l'effort de test en "amont" de cette façon est communément appelé "Conception pilotée par le test" (test-first design). Quels en sont les avantages ?

  1. Quelle que soit l'attention que vous porterez à la conception du logiciel, vous ferez des erreurs. Vous pouvez laisser passer un fait pertinent. Ou vous pouvez avoir l'habitude de réfléchir d'une façon particulière qui vous empêche d'envisager certaines alternatives. Ou vous pouvez être tout simplement fatigué et omettre quelque chose. Faire réviser vos artefacts de conception par d'autres personnes est utile. Elles peuvent avoir pensé aux faits que vous avez laissés passer ou bien remarquer un oubli. Le mieux est que ces personnes aient un point de vue différent du vôtre ; en envisageant la conception différemment, elles verront ce que vous avez omis.

    L'expérience a montré que l'optique du test est efficace. Elle est implacablement concrète. Pendant la conception du logiciel, il est facile de penser à une zone particulière comme "affichant le titre du client actuel" et de poursuivre sans vraiment y penser. Pendant la conception du test, vous devez décider spécifiquement de ce que cette zone affichera lorsqu'un client retraité de la Marine qui a ensuite obtenu une maîtrise de droit insiste qu'on l'appelle "Lieutenant Morton H. Throckbottle (Ret.), Esq." Son titre est-il "Lieutenant" ou "Esquire"?

    Si la conception du test est retardée jusqu'à juste avant de l'exécution du test, comme dans la Figure 1, vous perdrez probablement de l'argent. Une erreur dans la conception de votre logiciel ne sera pas décelée jusqu'à la conception du test, quand un testeur dira, "Vous savez, je connaissais ce type de la Marine...", créera le test "Morton" et découvrira le problème. C'est alors qu'une implémentation partiellement ou entièrement terminée doit être réécrite et qu'un artefact de conception doit être mis à jour. Cela coûterait moins chez de détecter le problème avant le début de l'implémentation.

  2. Certaines erreurs peuvent être décelées avant la conception du test. A la place, elles seront alors décelées par l'implémenteur. C'est encore mauvais. L'implémentation doit s'arrêter progressivement alors que l'attention passe de comment implémenter la conception à ce que devrait être la conception. Cela est gênant, même lorsque les rôles d'implémenteur et de concepteur sont assurés par la même personne ; et c'est encore plus gênant lorsque ce sont des personnes différentes. La Conception pilotée par le test aide à améliorer l'efficacité en évitant ce désagrément.

  3. Les conceptions de test aident les implémenteurs d'une autre façon : en clarifiant la conception. Si l'implémenteur a une question sur la signification de la conception, la conception de test peut servir d'exemple spécifique du comportement souhaité. Cela aura pour résultat un nombre inférieur de bogues provenant d'une incompréhension de l'implémenteur.

  4. Il y a moins de bogues même si l'implémenteur n'avait pas cette question en tête alors qu'il aurait dû. Par exemple, il peut y avoir eu une ambiguïté que le concepteur a interprété inconsciemment d'une façon et l'implémenteur d'une autre. Si l'implémenteur travaille à la façon à partir de la conception et des instructions spécifiques sur ce que le composant est supposé faire (à partir des cas de test), le composant est davantage susceptible de faire réellement ce qu'on attend de lui.

Exemples Haut de la page

Voici quelques exemples vous vous donnez un aperçu de la Conception pilotée par le test.

Supposez que vous créez un système pour remplacer l'ancienne méthode consistant à"demander à la secrétaire" pour l'affectation des salles de réunion. L'une des méthodes de la classe MeetingDatabase s'appelle getMeeting, qui a cette signature :

Meeting getMeeting(Person, Time);

Une fois le nom de la personne et l'heure saisis, getMeeting renvoie que telle personne est prévue pour assister à la réunion à telle heure. Si la personne n'a rien de prévu, il renvoie l'objet spécial Meeting libre. Il existe des cas de test très simples :

  • La personne n'a aucune réunion à un moment donné. L'objet libre est-il renvoyé ?
  • La personne a une réunion à cette heure. La méthode renvoie-t-elle la bonne réunion ?

Ces cas de test n'ont rien de passionnant, mais ils doivent en fin de compte être essayés. Ils peuvent également être créés maintenant, en rédigeant le code de test réel qui sera exécuté ultérieurement. Le code Java pour le premier test peut ressembler à cela :

    // if not in a meeting at given time,
    // expect to be unscheduled.
    public void testWhenAvailable() {
        Person fred = new Person("fred");
        Time now = Time.now();
        MeetingDatabase db = new MeetingDatabase();
        expect(db.getMeeting(fred, now) == Meeting.unscheduled);
    }

Mais il y a des idées de test plus intéressantes. Par exemple, cette méthode recherche une correspondance. Chaque fois qu'une méthode recherche quelque chose, c'est une bonne idée de se demander ce qui devrait se passer si la recherche trouve plusieurs résultats. Dans ce cas, cela signifie se demander "Une personne peut-elle être à deux réunions en même temps ?" Cela semble impossible, mais poser cette question à la secrétaire sur ce cas peut susciter une réponse surprenante. Il apparaît que certains cadres ont assez souvent deux réunions prévues au même moment. Leur rôle est de passer à la réunion, de "rallier les troupes" pendant un certain temps puis de passer à la deuxième. Un système qui n'avait pas prévu ce comportement sera inutilisé, du moins partiellement.

Voici un exemple de Conception pilotée par le test réalisée au niveau de l'implémentation et qui met le doit sur un problème d'analyse. Il y a plusieurs remarques à faire :

  1. Vous espérez qu'une bonne spécification et analyse de cas d'utilisation aura déjà découvert cette exigence. Dans ce cas, le problème aurait été évité "en amont" et getMeeting aurait été conçu différemment. (Il n'aurait pas renvoyé une réunion mais une série de réunions.) Mais l'analyse manque toujours certains problèmes, et il vaut mieux qu'ils soient découverts pendant l'implémentation qu'après le déploiement.

  2. Dans de nombreux cas, les concepteurs et implémenteurs n'auront pas la connaissance du domaine qui leur permettra de déceler de tels problèmes; ils n'auront pas l'occasion ou le temps d'interroger la secrétaire. Dans ce cas, la personne qui conçoit les tests pour getMeeting se demandera, "existe-il un cas dans lequel deux réunions doivent être renvoyées ?", réfléchira un moment et conclura que non. Ainsi, la Conception pilotée par le test ne décèle pas tous les problèmes mais le simple fait de se poser les bonnes questions augmente la chance de trouver un problème.

  3. Certaines des mêmes techniques de test qui s'appliquent pendant l'implémentation s'appliquent aussi à l'analyse. La Conception pilotée par le test peut être faite par des analystes mais ce n'est pas l'objet de cette page.

Le deuxième des trois exemples est un modèle de diagramme d'état-transition d'un système de chauffage.

Diagramme d'état-transition chauffage, ventilation et climatisation

Fig3 : diagramme d'état-transition chauffage, ventilation et climatisation

Un ensemble de tests traversera tous les arcs de ce diagramme d'état-transition. Un test peut commencer avec un système au ralenti, intégrer un événement Trop chaud, faire échouer le système pendant l'état Refroidissement/Marche, résoudre la panne, intégrer un autre événement Trop chaud puis refaire fonctionner le système au ralenti. Etant donné que tous les arcs ne sont pas concernés, d'autres tests sont nécessaires. Ces genres de tests recherchent différents types de problèmes d'implémentation. Par exemple, en traversant chaque arc, ils vérifient si l'implémentation en a laissé un de côté. En utilisant des séquences d'événements qui ont des chemins de panne suivis de chemins qui doivent aboutir positivement, ils vérifient que le code de traitement des erreurs arrive bien à résoudre des résultats partiels qui pourraient les calculs ultérieurs. (Pour en savoir plus sur le test des diagrammes d'état-transition, voir ../../modguide/md_tstidssttact.htm -- This hyperlink in not present in this generated websiteConseils et principes : Idées de tests pour diagramme d'état-transition et d'activités.)

Le dernier exemple utilise une partie d'un modèle de conception. Il y a une association entre un créditeur et une facture, où tout créditeur donné peut avoir plus d'une facture impayée.

Diagramme d'association des classes créditeur et facture

Fig4 : Association entre les classes créditeur et facture

Les tests basés sur ce modèle mettront le système à l'épreuve quand un créditeur n'aura aucune facture, une facture et un grand nombre de factures. Un testeur se demandera également s'il existe des situations dans lesquelles une facture doit être associée à plusieurs créditeurs, ou dans lesquelles une facture n'a aucun créditeur. (Il est possible que les personnes qui gèrent le système papier que le système informatique doit remplacer utilisent des factures sans créditeur pour le suivi du travail en cours). Dans ce cas, ce serait un autre problème qui aurait dû être décelé dans l'analyse.

Qui réalise la Conception pilotée par le test ? Haut de la page

La Conception pilotée par le test peut être réalisée soit par l'auteur de la conception soit par quelqu'un d'autre. Mais c'est généralement l'auteur qui s'en charge. L'avantage est que cela réduit les coûts de communication. Le concepteur d'artefact et le concepteur de test n'ont pas besoin de s'expliquer mutuellement les choses. De plus, un concepteur de test séparé devra passer du temps pour bien prendre connaissance de la conception alors que le concepteur d'origine la connaît déjà. Finalement, beaucoup de ces questions (comme "que se passe-t-il si le compresseur tombe en panne à l'état X ?") sont des questions naturelles à poser pendant la conception de l'artefact logiciel et la conception du test. La même personne peut donc les poser une seule fois et écrire les réponses sous la forme de tests.

Il existe cependant des inconvénients. Le premier est que le concepteur d'artefacts est, dans une certaine mesure, aveugle à ses propres erreurs. Le processus de conception de test révélera une partie de ce qu'il ne voit pas mais probablement pas autant que ce que trouverait une autre personne. L'importance de ce problème semble varier énormément d'une personne à l'autre et est souvent liée à l'expérience du concepteur.

Un autre inconvénient d'utiliser la même personne pour la conception du logiciel et du test est l'absence de parallélisme. Alors que l'affectation des rôles à des personnes différentes nécessitera un effort total supérieur, cela aura certainement pour conséquence un gain de temps sur le calendrier du projet. Si les personnes sont impatientes de terminer la conception et de passer à l'implémentation, passer du temps pour la conception du test peut être frustrant. Plus important encore, le travail a tendance à être bâclé pour passer à autre chose.

Toute la conception du test peut-elle être réalisé au moment de la phase de conception du composant ? Haut de la page

Non. La raison est que toutes les décisions ne sont pas prises à la phase de conception. Les décisions prises pendant l'implémentation ne seront pas bien testées par les tests créés à partir de la conception. L'exemple classique est un programme de routine pour trier des tableaux. Il existe de nombreux algorithmes de tri différents avec des substitutions possibles différentes. Quicksort est généralement plus rapide qu'un tri par insertion sur les grands tableaux mais il est souvent plus lent sur les petits tableaux. Ainsi, un algorithme de tri peut être implémenté pour utiliser Quicksort pour les tableaux possédant plus de 15 éléments et le tri par insertion autrement. Cette division du travail peut être invisible des artefacts de conception. Vous pouvez la représenter dans un artefact de conception mais le concepteur peut avoir décidé que l'avantage de prendre de telles décisions explicites n'en valait pas la peine. Etant donné que la taille du tableau ne joue aucun rôle dans la conception, la conception du test peut utiliser par inadvertance uniquement les petits tableaux, ce qui signifie qu'aucun code Quicksort ne sera testé.

Comme autre exemple, considérez cette fraction d'un diagramme de séquence. Elle montre un ResponsableSécurité en train d'appeler la méthode journal() de StableStore. Dans ce cas, toutefois, le journal() renvoie une panne qui oblige le ResponsableSécurité à appeler Connection.close().

Instance de diagramme de séquence de responsable sécurité

Fig5 : Instance de diagramme de séquence de ResponsableSécurité

Cela est un bon rappel pour l'implémenteur. Chaque fois que journal() échoue, la connexion doit être fermée. La question pour le test est si l'implémenteur l'a vraiment fait, et s'il l'a fait correctement, dans tous les cas ou seulement dans certains. Pour répondre à la question, le concepteur de test doit trouver tous les appels vers StableStore.log() et s'assurer que chacun de ces points d'appel reçoit une panne à gérer.

Cela peut sembler étrange d'exécuter un tel test, étant donné que vous venez d'examiner le code qui appelle StableStore.log(). Ne pouvez-vous pas seulement vérifier pour voir s'il gère la panne correctement ?

Peut-être qu'une inspection suffirait. Mais le code de gestion des erreurs est réputé pour être sujet aux erreurs car il dépend souvent implicitement d'hypothèses que l'existence de l'erreur a été violée. L'exemple classique est un code qui gère les anomalies d'affectation. Voici un exemple :

while (true) { // top level event loop
    try {
        XEvent xe = getEvent();
        ...                      // main body of program
    } catch (OutOfMemoryError e) {
        emergencyRestart();
    }
}

Ce code tente de récupérer des erreurs de la mémoire en nettoyant (rendant ainsi la mémoire disponible) puis en continuant de traiter les événements. Supposons que cette conception est acceptable. emergencyRestart prend soin de ne pas affecter de mémoire. Le problème est que emergencyRestart appelle un utilitaire de routine qui en appelle un autre qui en appelle un autre, ce qui affecte un nouvel objet. Sauf s'il n'y a pas de mémoire, le programme échoue complètement. Ces types de problèmes sont difficiles à trouver à travers une inspection.

Conception pilotée par le test et les phases de RUP Haut de la page

Jusqu'à ce point, nous avons implicitement supposé que vous travaillerez à la conception du test autant que possible et le plus tôt possible. C'est-à-dire que vous dériverez tous les tests que vous pouvez de l'artefact de conception, n'ajoutant ensuite que les tests basés sur les éléments internes à l'implémentation. Cela peut ne pas être approprié dans la phase d'Elaboration , car des tests aussi complets peuvent ne pas être alignés avec les objectifs de l'itération.

Supposons qu'un prototype architectural est créé pour démontrer la faisabilité du produit à des investisseurs. Il peut se baser sur quelques instances de cas d'utilisation clés. Le code doit être testé pour voir s'il les prend en charge. Mais la création d'autres test peut-elle faire des dégâts ? Par exemple, il peut être évident que le prototype ignore ces cas d'erreurs importants. Pourquoi ne pas documenter le besoin de traiter ces erreurs en rédigeant des cas de tes qui les mettront à l'épreuve ?

Mais que se passe-t-il si le prototype fait son travail et révèle que l'approche architecturale ne fonctionnera pas ? L'architecture sera ensuite abandonnée, ainsi que tous les tests pour la gestion des erreurs. Dans ce cas, l'effort de conception de tests n'aura abouti à rien. Il aurait mieux valu attendre et ne concevoir que les tests nécessaires pour vérifier que prototype démonte le bien-fondé de la conception.

Cela peut sembler minime mais les effets psychologiques en jeu sont importants. La phase d'Elaboration consiste à aborder les principaux risques. Toute l'équipe du projet doit être concentrée sur ces risques. Demander à des personnes de se concentrer sur des questions de moindre importance détourne l'équipe de son point d'attention et lui fait perdre de l'énergie.

Alors, quand la Conception pilotée par le test peut-elle être utilisée avec succès dans la phase d'Elaboration ? Elle peut jouer un rôle important dans l'exploration adéquate des risques architecturaux. Considérer comment l'équipe saura précisément si un risque a été pris en compte ou évité clarifiera le processus de conception et pourrait bien avoir pour conséquence la création d'une meilleure architecture du premier coup.

Pendant la phase de Construction, les artefacts de conception prennent leur forme finale. Toutes les réalisations de cas d'utilisation requises sont implémentées tout comme les interfaces pour toutes les classes. Etant donné que l'objectif de la phase est l'achèvement, une Conception pilotée par le test complète est appropriée. Les événements ultérieurs devraient invalider quelques tests, voire aucun.

Les phases de Création et de Transition sont généralement moins axées sur les activités de conception qu'il convient de tester. Quand c'est le cas, la Conception pilotée par le test s'applique. Par exemple, elle peut être utilisée dans le travail visant à démontrer le bien-fondé de la conception dans la phase de Création. Comme avec les tests des phases Construction et Elaboration, elle doit être alignée avec les objectifs de l'itération.



RUP (Rational Unified Process)   2003.06.15