<string>

Table des matières

Mission 10 - Polymorphisme

Mission 10

Mission 10

1   Introduction

1.1   Objectifs

Dans cette mission, vous allez approfondir votre compréhension de concepts importants de la programmation orientée objets déjà abordés dans les deux missions précédentes, ainsi que quelques nouveaux concepts :

  • les classes, objets, variables et méthodes d'instance ;
  • les variables et méthodes de classe ;
  • les variables et méthodes privés, les méthodes accesseurs et mutateurs ;
  • la portée des variables d'instance ;
  • l'héritage, la redéfinition et l'écrasement de méthodes, le polymorphisme ;
  • la liaison dynamique et les subtilités de la sémantique de self et super().

Vous apprendrez également à écrire des tests unitaires pour vos programmes orientés objets.

1.2   Préparation, étude et apprentissage

Si vous n'avez pas encore eu le temps de relire en détail les différents chapitres de la partie Objects du syllabus en ligne, maintenant est venu le moment de rattraper votre retard. Le syllabus explique bien différents concepts, sur un autre exemple que les exemples utilisés dans le cours magistral :

On vous conseille également de tester et de jouer avec les différents exemples de code qui se trouvent dans les annexes suivantes, venant soit du syllabus en ligne soit correspondant aux exemples du cours magistral :

Dans cette mission, vous devrez également utiliser le framework unittest pour produire des classes de test. En préparation de cette mission, consultez donc la documentation de ce cadre de test, et essayez de faire quelques classes test simple en utilisant cet approche. Un bon tutoriel couvrant, entre autres, les tests unitaires avec unittest se trouve ici .

2   Questionnaire de démarrage

2.1   Questions à choix multiple

Les questions à choix multiples de cette mission sont accessibles en ligne depuis https://inginious.info.ucl.ac.be/course/LSINF1101-PYTHON/Session10_2019_QCM


        
        

2.2   Questions ouvertes

2.2.1   Vocabulaire

On vous donne le code suivant qui teste certaines fonctionnalités du module random.

import unittest
import random

class RandomTest(unittest.TestCase):
    """Classe de tests utilisée pour tester les fonctions du module 'random'"""

    def setUp(self):
        """Initialisation des tests."""
        self.l = list(range(10))

    def test_choice(self):
        """Test de fonctionnement de la fonction random.choice"""
        e = random.choice(self.l)
        # Vérifie que 'e' est dans 'l'
        self.assertIn(e,self.l)

    def test_shuffle(self):
        """Test le fonctionnement de la fonction random.shuffle"""
        random.shuffle(self.l)
        self.l.sort()
        self.assertEqual(self.l, list(range(10)))

    def test_sample(self):
        """Test le fonctionnement de la fonction random.sample"""
        extrait = random.sample(self.l, 5)
        for element in extrait:
            self.assertIn(element,self.l)
        self.assertRaises(ValueError, random.sample, self.l, 20)

# Pour lancer automatique ces tests quand on exécute ce fichier
if __name__ == '__main__':
    unittest.main()

Considérant cette classe, on vous demande de :

  • Expliquez cet extrait de code

    if __name__ == '__main__':
        unittest.main()
    
 
 
 
 

  • Expliquez le rôle de import unittest et pourquoi c'est indispensable.
 
 
 
 

  • Expliquez cette ligne class RandomTest(unittest.TestCase): et pourquoi c'est indispensable.
 
 
 
 

  1. def setUp(self)
 
 
 
 

  1. self.assertEqual()
 
 
 
 

  1. self.assertIn()
 
 
 
 

  1. self.assertRaises()
 
 
 
 

2.2.2   Tests unitaires

Implémentez une classe de tests unitaires MyClassTest qui hérite de unittest.TestCase qui teste la fonction carre_parfait du module totest. (En mathématiques, un carré parfait est le carré d'un entier.)

Soit la fonction carre_parfait du module totest ayant pour spécifications :

def carre_parfait (x) :
    '''
    retourne true si l'entier en argument est un carré parfait, false sinon
    '''
# CODE NON FOURNI

Complétez la classe de test ci-dessous :

import totest
import unittest

class MyClassTest(unittest.TestCase) :

    # todo

if __name__ == '__main__':
    unittest.main()
  • Ecrivez au moins trois tests unitaires pour vérifier l'implémentation de cette fonction
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

  • Que donnent-vos tests unitaires pour les implémentations suivantes :

    1. def carre_parfait (x) :
         return True
      
    2. def carre_parfait (x) :
          return False
      
    3. def carre_parfait (x) :
          cur = 0
          while not cur*cur == x :
              cur += 1
          return True
      
    4. import math
      def carre_parfait (x) :
          root = math.sqrt(x)
          return root.is_integer()
      
    5. import math
      def carre_parfait (x) :
          if x < 0 : return False
          root = math.sqrt(x)
          return root.is_integer()
      

2.2.3   Centre d'expédition d'Amazon


        
        

Mission 10

Mission 10

1   Description

L'objectif de cette mission est de créer des robots virtuels maintenant un état représentant leur position à l'écran et des opérations permettant de les faire bouger sur l'écran. On peut se baser sur plusieurs bibliothèques graphiques pour implémenter de tels robots. Cette mission vise à illustrer que, grace au polymorphisme, il devient relativement facile de remplacer une telle bibliothèque par une autre sans devoir modifier beaucoup de code.

On vous fournira une première implémentation basé sur la bibliothèque graphics.py de Python. A vous de proposer une implémentation alternative en utilisant la bibliothèque turtle. Ces deux types de robots sont polymorphe: ils comprennent exactement les mêmes messages, mais l'un est implémenté avec des positions absolues sur un plan XY, tandis que l'autre utilise des positions relatives.

Ensuite on vous demandera d'étendre davantage la fonctionnalité de ces robots, ce qui vous permettra de découvrir la puissance du concept de l'héritage pour la réutilisation du code.

Il sera aussi important de bien tester à la fois les classes qui vous sont fournies et que vous implémenterez, avec des tests unitaires. C'est-à-dire que vous créerez, par classe à tester, une classe test correspondante, avec des méthodes tests pour chacune des méthodes de la classe à tester. Ceci vous permet de tester, à une granularité fine, les différents unités (les classes et les méthodes) de votre code orienté objet.

Les différents concepts traités dans cette mission seront, entre autres : le polymorphisme, la composition, la délégation, l'héritage, la duplication et la réutilisation de code, la substitutabilité, l'utilisation de self et de super(), et les tests unitaires. Vous travaillerez par groupes de deux.

2   Etapes

2.1   Avant de commencer...

Avant de commencer, installez le package graphics.py permettant de dessiner des figures simples dans une fenêtre graphique. Ce package sera utilisée par la classe XYRobot. Vous pouvez installer ce package facilement depuis Thonny en suivant les étapes suivantes: (1) Sélectionner le menu Tools > Manage Packages... en Thonny; (2) taper graphics.py dans le champs de recherche pour retrouver le bon package et appuyez sur Install . (On a besoin du package graphics.py développé par John Zelles pour utilisation avec son livre Python Programming: An Introduction to Computer Science. La documentation pour ce package se trouve ici .)

2.2   La classe XYRobot

La classe XYRobot vous est fournie dans le fichier XYRobot.py sur le site inginious où vous pouvez remettre votre solution. Analysez le code de cette classe attentivement. Elle :

  • Utilise le package graphics.py que vous avez installé dans l'étape prédédente.

  • Utilise des fonctions goniométriques comme cos et sin ainsi que la constante mathématique pi, fournies par le module math.

  • Représente un robot virtuel ayant un nom, une position (x, y), et une direction (angle). Quand il bouge, ce robot dessine ses mouvements dans une fenêtre représentant un plan XY.

  • En plus des méthodes habituelles __init__ et __str__ ainsi que des méthodes accesseurs et mutateurs, cette classe implémente des méthodes comme:

    • move_forward(distance) : fait avancer le robot de distance pixels et trace une ligne lors de ce mouvement
    • move_backward(distance) : fait reculer le robot de distance pixels et trace une ligne lors de ce mouvement
    • turn_left() : fait tourner le robot de 90 degrés vers la gauche (dans le sens contraire des aiguilles d'une montre)
    • turn_right() : fait tourner le robot de 90 degrés vers la droite (dans le sens des aiguilles d'une montre)

Appeler ces méthodes fait bouger le robot et dessine des figures sur un plan XY. Il suffit d'exécuter le code fourni dans le fichier pour avoir un exemple concret. N'oubliez pas d'installer la bibliothèque graphics.py avant d'exécuter ce fichier.

2.3   Tester la classe XYRobot

Tandis que l'implémentation de la classe XYRobot vous est fournie, il manque une classe avec des tests unitaires afin de vérifier que le robot bouge et met à jour ses attributs correctement. L'objectif de cette étape sera donc de créer une classe test avec des tests unitaires pour la classe qui vous est fournie.

Implémentez cette classe de test TestXYRobot afin de vérifier le comportement correct des différentes méthodes de la classe XYRobot. Pour réaliser vos tests, il est important à savoir que le package ``graphics.py`` considère que le point avec position (x=0,y=0) se trouve dans le coin supérieur gauche de la fenêtre.

Position de l'origine et orientation du plan XY pour la bibilothèque ``graphics``

Par exemple, quand on crée un nouveau robot r2d2 = XYRobot("R2-D2") il se trouvera à la position (x=0,y=0) avec comme angle 0 (direction Est). Après avoir exécuté r2d2.move_forward(50) le robot devrait se trouver à la position (x=50, y=0), approximativement, à cause des arrondis dans les calculs. Ensuite, après avoir exécuté r2d2.turn_right() l'angle du robot sera 90° (approximativement), etc.

Soyez le plus complet possible dans vos tests. Mettez votre classe test TestXYRobot dans un fichier séparé TestXYRobot.py qui s'occupera de charger le fichier XYRobot.py et d'exécuter les différents tests sur la classe XYRobot.

2.4   La classe TurtleBot

Maintenant c'est à vous d'implémenter une classe TurtleBot qui est polymorphe avec la clase XYRobot de l'étape précédente. C'est-à-dire que, en plus des méthodes __init__ et __str__ et des méthodes accesseurs (dont angle() et position()) et mutateurs, elle doit implémenter les mêmes méthodes :

  • move_forward(distance) : fait avancer le robot de distance pixels et trace une ligne lors de ce mouvement
  • move_backward(distance) : fait reculer le robot de distance pixels et trace une ligne lors de ce mouvement
  • turn_left() : fait tourner le robot de 90 degrés vers la gauche (dans le sens contraire des aiguilles d'une montre)
  • turn_right() : fait tourner le robot de 90 degrés vers la droite (dans le sens des aiguilles d'une montre)

La différence majeure est que la classe TurtleBot fait ces dessins via un objet de type Turtle. Cette classe sera donc assez facile à implémenter en utilisant un objet de type Turtle comme attribut. Le travail d'avancer, tourner et dessiner pourra alors être délégué à cette tortue. Pour rappel, la classe Turtle du package turtle.py fournit des méthodes comme position, heading, forward, backward, right et left. (Voici la documentation pour ce package.)

Pour implémenter la classe ``TurtleBot`` et effectuer vos tests, il est important à savoir que dans la fenêtre graphique du turtle, la position (x=0,y=0) se trouve au milieu de la fenêtre, l'axe X pointe vers la droite et l'axe Y vers le haut.

Position de l'origine et orientation du plan XY pour la bibilothèque ``turtle``

2.5   Tester la classe TurtleBot

Pour tester le comportement correct de la classe TurtleBot, on vous fournit un fichier TestTurtleBot.py (ce fichier se trouve sur le site inginious où vous pouvez remettre votre solution.) Ce fichier test contient des tests unitaires pour votre classe TurtleBot qui doit se trouver dans un fichier TurtleBot.py.

Vous pouvez aussi vérifier que le comportement de votre robot est correct en vérifiant que ce qu'il dessine sur l'écran correspond à ce que vous attendez.

2.6   Etendre les classes XYRobot et TurtleBot

Maintenant, on veut étendre la fonctionnalité des robots. A vos deux classes XYRobot et TurtleBot , ajoutez des méthodes pour:

  • garder une trace des actions exécutées auparavant par ce robot;
  • rejouer ces actions à l'envers (c'est-à-dire de défaire ces actions).

Plus spécifiquement, il faut ajouter les deux méthodes suivantes:

  • history() : retourne l'historique des actions précédentes, comme une liste du style: [('forward', 50), ('left', 90), ('forward', 50), ...]
  • unplay() : rejouer les actions précédentes dans le sens inverse et vider l'historique après avoir rejoué ces actions.

Même si faire du copier-coller de code entre ces deux classes est encore permis pour cette exercice, dans l'étapes suivantes on utilisera l'héritage pour éviter ce copier-coller en ajoutant une classe mère avec le code commun entre ces deux classes.

2.7   Tester les classes XYRobot et TurtleBot

Ajouter aux deux classes de test TestXYRobot et TestTurtleBot les tests unitaires nécessaires pour tester si les deux classes XYRobot et TurtleBot, avec la fonctionnalité historique et la fonctionnalité pour défaire les actions, marchent correctement.

2.8   Restructurer les classes XYRobot et TurtleBot

Si vous n'avez pas encore utilisé l'héritage lors de l'étape précédente, il est fort probable que vous ayez créé des méthodes très similaires dans les deux classes XYRobot et TurtleBot. Ces deux classes représentent tous les deux un robot ayant une mémoire contenant les actions déjà effectuées ainsi qu'une méthode pour défaire ces actions. Une bonne manière d'éviter le code redondant (recopié/dupliqué dans les deux classes) est de le mettre dans un classe mère Robot commune à ces deux classes, et de déclarer XYRobot et TurtleBot comme des classes filles de cette classe. En particulier, la classe mère peut contenir tout le code pour retenir les actions effectuées et de les refaire dans le sens inverse.

Lors de cet étape on vous demande donc de :

  • Créer une nouvelle classe mère Robot qui doit se trouver dans un fichier Robot.py.
  • Réécrivez les classes XYRobot et TurtleBot comme sous-classes de Robot.
  • Bouger le code qui est commun entre les classes filles XYRobot et TurtleBot vers la classe mère Robot.

2.9   Retester les classes XYRobot et TurtleBot

Rejouez les tests unitaires TestXYRobot et TestTurtleBot de l'étape précédente sur la nouvelle version des classes XYRobot et TurtleBot qui héritent de leur nouvelle clases mère. Si tout va bien ces tests doivent toujours fonctionner. Si ce n'est pas le cas, adapter le code ou les tests où nécessaire.

3   Diagramme de classes

Le diagramme de classes suivant résume les différentes classes et méthodes à implémenter lors de cette mission :

/syllabus/info1-exercises/assets/class_diagram_mission10.png

4   Remise de votre solution

  1. Un fichier Robot.py qui contient la classe mère Robot avec le code de gestion de l'historique.

    (Si vous n'êtes pas arrivé à cette étape, remettez simplement un fichier vide ici.)

  2. Un fichier XYRobot.py qui contient la classe XYRobot qui hérite de Robot.

    (Si vous n'êtes pas arrivé à l'étape avec l'héritage de la classe Robot, remettez la version de votre classe XYRobot sans héritage ici.)

  3. Un fichier TurtleBot.py qui contient la classe TurtleBot qui hérite de Robot.

    (Si vous n'êtes pas arrivé à l'étape avec l'héritage de la classe Robot, remettez la version de votre classe TurtleBot sans héritage ici.)

  4. Un fichier TestXYRobot.py qui contient les tests unitaires pour votre classe XYRobot.

    Ce fichier TestXYRobot.py doit charger le fichier XYRobot.py et exécuter les différents tests unitaires sur la classe XYRobot.

  5. Un fichier TestTurtleBot.py qui contient les tests unitaires pour votre classe TurtleBot.

    Ce fichier TestTurtleBot.py doit charger le fichier TurtleBot.py et exécuter les différents tests unitaires sur la classe TurtleBot.


        
        
Questions complémentaires

Questions complémentaires

Héritage