<string>

Table des matières

Mission 10 - Polymorphisme

Mission 10

Mission 10

Introduction

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 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 aurez également l'occasion d'écrire des tests unitaires pour vos programmes orientés objets.

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 (en français) sur les tests unitaires avec unittest se trouve également ici Créez des tests unitaires avec unittest

Questionnaire de démarrage

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


        
        

Questions ouvertes

Question 1 : Vocabulaire

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

import unittest
import random

class RandomTest(unittest.TestCase):
    """Clases de test utilisé 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)

# To automatically launch this test when executing this file
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()
 
 
 
 

Question 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()
      

Question 3 : Amazon

As the new manager of an Amazon dispatch center, you have to create a class to represent the commands.

A command have the following attributes :

  • the id of the buyer
  • the id of the item
  • the quantity of the item
  • the price of the item

And must have the following methods :

  • get_price() : return the total price of the command
  • __str__() : return the command on the format : id_buyer, id_item : price * quantity = (price*quantity)

Example : "12, 32 : 5 * 20 = 100"

There must also be class methods :

  • get_number_total_commad() : return the total number of commands made
  • get_total_price() : return the total price of all the commands


        
        

Mission 10

Mission 10

L'objectif de cette mission est de créer des robots 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. On vous fournira une première implémentation basé sur la bibliothèque graphics.py de Python. A vous de proposer une implémentation similaire 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 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 les classes qui vous sont fournies ou 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.

Etapes

Etape 1 : Tester la classe XYRobot

L'objectif de cette première étape sera de créer une classe test TestXYRobot avec des tests unitaires pour une classe XYRobot qui vous est fournie.

  • Avant de commencer, vous devez installer le package graphics.py permettant de dessiner des figures simples dans une fenêtre graphique. Cette bibliothèque 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 .

  • La classe XYRobot utilise aussi des fonctions mathématiques comme math.cos et math.sin ainsi que des constantes mathématiques comme math.pi, fournies par la bibliothèque math.

  • La classe XYRobot (dont l'implémentation se trouve dans le fichier XYRobot.py qui vous est fourni sur le site inginious où vous pouvez remettre votre solution) représente un robot 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 __init__ et __str__ et des méthodes accesseurs et mutateurs, cette classe implémente des méthodes comme:

    moveforward(distance) :

    fait avancer le robot de distance pixels et trace une ligne lors de ce mouvement

    movebackward(distance) :

    fait reculer le robot de distance pixels et trace une ligne lors de ce mouvement

    turnleft() :

    fait tourner le robot de 90 degrés vers la gauche (dans le sens contraire des aiguilles d'une montre)

    turnright() :

    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 d'abord la bibliothèque graphics.py avant d'exécuter ce fichier.

L'implémentation de cette classe XYRobot vous est fournie mais il manque une classe avec des tests unitaires afin de vérifier que le robot bouge et met à jour ses attributs correctement. Implémentez cette classe de test TestXYRobot afin de vérifier le comportement correct des différentes méthodes de la classe XYRobot.

Par exemple, quand on crée un nouveau robot r2d2 = XYRobot("R2-D2") il se trouvera à la position (x=100,y=100) avec comme angle 0 (direction Est). Après avoir exécuté r2d2.moveforward(50) le robot devrait se trouver à la position (x=150, y=100), approximativement, à cause des arrondis dans les calculs. Ensuite, après avoir exécuté r2d2.turnleft() 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.

Etape 2 : implémenter une classe TurtleBot

Dans cette deuxième étape 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 getangle() et position()) et mutateurs, elle doit implémenter les mêmes méthodes :

moveforward(distance) :
fait avancer le robot de distance pixels et trace une ligne lors de ce mouvement
movebackward(distance) :
fait reculer le robot de distance pixels et trace une ligne lors de ce mouvement
turnleft() :
fait tourner le robot de 90 degrés vers la gauche (dans le sens contraire des aiguilles d'une montre)
turnright() :
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é à ce turtle.

Pour tester son comportement correct, on vous fournit une 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.

Etape 3 : étendre 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 action à l'envers (c'est-à-dire de défaire ces actions).

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

getHistory(self) :

retourne l'historique des actions précédentes, comme une liste du style:

[('forward', 50), ('left', 90), ('forward', 50), ...]

unplay(self) :
rejouer les actions précédentes dans le sens inverse et vider l'historique après avoir rejoué ces actions

Etape 4

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.

Etape 5

Si vous n'avez pas encore utilisé l'héritage lors de l'étape 3, 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.

Etape 6

Rejouez les tests unitaires de l'étape 4 sur les nouvelle classes XYRobot et TurtleBot qui héritent de la 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.

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é à l'étape 5, 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 5, remettez la version de votre 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 5, remettez la version de votre 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.