Friday, June 26, 2009

Pharo et tests unitaires: utilisation des outils

Voici le premier billet d'une petite trilogie d'introduction sur l'écriture  de tests unitaires dans Pharo. La série abordera les sujets suivants:
  • utilisation des outils de test et debuggage (ce billet)
  • écriture et test d'une Collection
  • utilisation des Traits dans les tests unitaires

Pour illustrer mes propos, je prendrai l'exemple extrêmement original de la manipulation d'une liste de films. Je veux créer des films, les ajouter à une liste et connaître le nombre total de films

Création du package Movies:


Pour commencer, créons le package Movies dans lequel nous mettrons toutes nos classes.  Ouvrez le Class Browser, clic-droit dans la première colonne et sélectionnez create package. Saisissez Movies comme nom puis valider.

Le package Movies apparaît dans la liste classée par ordre alphabétique. Comme nous allons l'utiliser souvent, plaçons-le en tête de liste pour le retrouver facilement. Clic-droit sur le package et sélectionnez place package on top.


Notre premier test unitaire:

Passons ensuite à la classe Movie. Un film ayant un titre, nous allons vérifier qu'une instance de Movie possède un accesseur title qui retourne la chaîne passée à la construction de l'instance.

En développement piloté par les tests, on commence par écrire ... le test.

Déclarons une classe MovieTest qui hérite TestCase (classe de base pour les tests unitaires). Dans le panneau d'édition du Class Browser, saisissez le code suivant:

TestCase subclass: #MovieTest
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Movies'

Pour notre test, nous créons deux instances de la classe Movie en spécifiant leurs titres respectifs. Nous vérifions que les titres ont bien été assignés en utilisant la méthode assert de TestCase. Ajoutons la procédure testMovieHasATitle comme ceci:

testMovieHasATitle
    | starWars bladeRunner |
    starWars:= Movie newWithTitle: 'Star Wars'.
    bladeRunner:= Movie newWithTitle: 'Blade Runner'.

    self assert: starWars title = 'Star Wars'.
    self assert: bladeRunner title = 'Blade Runner'


Lors de la sauvegarde de la procédure, Pharo détecte que la classe Movie utilisée n'existe pas et nous propose de la déclarer. Ne nous en privons pas.

Pharo demande de saisir la définition de la classe et propose Object subclass: #Movie, ce qui conviens dans notre cas. De même pour la confirmation d'ajout de la classe dans le package Movie.

Lançons maintenant notre test unitaire. Clic droit sur la classe MovieTest et sélectionnez run the tests (si vous préférez les raccourcis clavier, tapez Ctl+t).

 
Pharo lance le test, détecte une erreur et nous propose d'ouvrir le debugger, ce que nous allons faire.

Implémentation de la classe Movie:
 
Le debugger nous présente la pile d'appel qui a conduit à l'erreur.  Comme par hasard ;), la pile d'appel contient notre méthode MovieTest#testMovieHasATitle. En cliquant dessus, le debugger affiche le détail de la méthode et indique la ligne source de l'exception: on appelle la méthode inexistante newWithTitle de la classe Movie.

Retournons dans le Class Browser pour l'ajouter. Sélectionnez la classe Movie. Etant donné qu'on veut créer une méthode de classe, n'oubliez pas de cliquez sur le bouton class. La méthode doit créer une nouvelle instance de la classe Movie, lui assigner son titre et retourner l'instance.
newWithTitle: aString
    | movie |
    movie := Movie new.
    movie title: aString.
    ^ movie

Une fois la méthode acceptée, retournez dans le debugger et cliquez sur le bouton Proceed pour continuez l'exécution du test là où elle s'était arrêtée (si vous avez fermé le debugger, relancez le test).

Cette fois, le debugger va plus loin et s'arrête dans notre nouvelle méthode Movie#newWithTitle car l'instance de Movie ne connaît pas la méthode title:aString. Ajoutons-là dans le Class Browser (n'oubliez pas de cliquer sur le bouton instance):
title: aString
    title := aString


Lorsque vous acceptez la méthode, Pharo indique qu'il ne connaît pas la variable title. Comme cette variable est un attribut d'instance, sélectionnez declare instance. Si vous cliquez de nouveau sur la classe Movie pour afficher sa définition, vous apercevrez notre nouvelle variable d'instance.

Object subclass: #Movie
    instanceVariableNames: 'title'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Movies'

Continuons l'exécution en cliquant sur Proceed. Nous venons de créer la méthode Movie#title: pour assigner le titre, il nous manque la méthode Movie#title pour le lire. Ajoutez là:
title
    ^ title

Cette fois-ci, victoire !!



Vous remarquerez le voyant vert indiquant que la totalité des tests de notre classe sont passés.

Notez aussi que l'implémentation de la classe Movie a été pilotée par le test: c'est en suivant les erreurs indiquées par le debugger que nous avons écrit le code réel. C'est une des raisons pour considérer le code de test plus important que le code réel.

"OH: the trick is to write unit tests while you're sober, then write the code while you're drunk.", auteur dont j'ai oublié de noter le nom.

Dans le prochain billet, nous écrirons la classe Movies qui recense les films et les tests associés.

2 comments:

  1. Cool!
    Now may be you should try the package developed by hernan wilkinson (see the pahro mailing-list) because it makes the TDD feel of pharo much better.

    ReplyDelete
  2. I suppose you're talking about this thread: http://n2.nabble.com/Making-pharo-more-tdd-oriented-td3115911.html
    It's appealing, I will give it a try for my next post.

    Thanks for the tip.

    ReplyDelete