TP de synthèse sur les tests unitaires
<note important>Ce TP sera noté.</note>
Partie 1. Préparation de l'environnement de travail
- Créez un répertoire nommé “Chaines”.
- Initialisez un dépôt Git local dans ce répertoire.
- Créez un fichier nommé “reponses.txt” dans le répertoire “Chaines”, inscrivez vos noms, prénoms et n° de groupe dans ce fichier.
- Récupérez le fichier password.py et test_password.py.
- Commitez ces fichiers.
Dans la suite du sujet, vous devez faire un commit à chaque fois que vous verrez le mot “COMMIT”.
À la fin du TP (et uniquement à ce moment là), vous devrez pousser l'ensemble de vos commits sur GitHub et m'envoyer l'URL de votre dépôt.
Partie 2. Les mots de passe d'Edward
Edward pense être un maniaque de la sécurité. Il change son mot de passe toutes les semaines. Mais pour ne pas avoir à se souvenir d'un nouveau mot de passe toutes les semaines, il a inventé un système qu'il croit astucieux : chaque mot de passe est construit à partir du précédent selon une règle simple : il incrémente d'un caractère son mot de passe précédent.
Par exemple, le mot de passe suivant “abcde” est “abcdf”, car “f” suit “e” dans l'ordre alphabétique.
Mais Edward est aussi un peu paresseux. Ses mots de passe ne contiennent que les 26 lettres de l'alphabet. Il n'utilise ni les majuscules, ni les caractères spéciaux. Par conséquent, lorsqu'il arrive à “z” et qu'il doit incrémenter, il repasse à “a”, et incrémente la lettre suivante à gauche (exactement comme on le ferait avec des nombres (le suivant de 19 est 20, et le suivant de 1999 et 2000).
Par exemple, le mot de passe suivant de “abczz” est “abdaa”.
Question 1.
Le fichier “pass.py” contient une implémentation de la fonction qui, étant donné un mot de passe donné en entré, retourne le mot de passe suivant selon les règles d'Edward.
1.1 Complétez les commentaires dans le code aux endroits notés “#1”, “#2” et “#3”.
1.2 Exécutez ce code et observez les résultats.
1.3 Corrigez le code pour qu'il passe tous les tests exprimés en doctest.
1.4. Dans le fichier “reponses.txt”, expliquez tous les cas qui pourraient selon vous faire planter ce code (on ne vous demande pas de corriger le code pour éviter les plantages).
1.5 COMMIT.
Question 2.
Pour des raisons de simplicité, on va considérer que les mots de passe d'Edward sont de longueur fixe. Par exemple, si l'on choisit arbitrairement que ses mots de passe seront de longueur 5, il n'existera pas de mot de passe suivant pour “zzzzz”. Une erreur devra donc être levée si on essaie de générer le successeur de “zzzzz”. Autrement dit, la taille du mot de passe généré est toujours égale à la taille du mot de passe source. Si ce n'est pas possible, une erreur appropriée est levée.
Le fichier “test_password.py” contient lui aussi des tests, mais en unittest cette fois-ci. Pour rappel, doctest permet d'écrire des “docstrings”, c'est-à-dire des petits tests simples qui ont l'avantage de documenter le code en même temps, alors que unittest vous donne accès à tout le panel des tests unitaires disponibles en Python.
2.1 Complétez les docstrings et les tests unitaires pour prendre en compte la nouvelle information et anticiper tous les cas potentiellement problématiques.
2.2 Assurez-vous que votre code passe bien tous les tests.
2.3 Pour un mot de passe de longueur N, combien de mots de passe différents peuvent être générés ? Répondez dans “reponse.txt”.
2.4 COMMIT.
Partie 3. Arrivée de Gunter.
Gunter est le nouvel admin système de la société loufoque ou travaille Edward. Il tombe dans les pommes quand il apprend la façon de faire de ce dernier. Quelques secondes après avoir repris ses esprits, il décide de mettre en place des règles sur les mots de passe. Edward ne comprend pas le sens de ces règles, car à part lui compliquer la vie, elles ne changent en rien les problèmes évidents de sécurité de son système… mais qu'importe, le chef l'a dit, alors il les met en place.
Voici les règles :
* Chaque mot de passe doit contenir au moins une série continue de 3 lettres comme abc ou cde. abd n'est pas une suite valide, car elle n'est pas continue.
* Les lettres i, o et l sont strictement interdites, car elle peuvent être mélangées avec d'autres caractères… donc on ne doit pas les employer. Le mot de passe “joli” est donc impensable.
* Chaque mot de passe doit contenir au moins deux paires de lettres, qui ne se recouvrent pas. Par exemple, aagcc est valide selon ce critères, mais baaac ne l'est pas.
* Un même caractère peut être utilisé dans une paire et dans une suite.
Exercice 1. Génerer un mot de passe valide
Edward demande à Joséphine de lui écrire les méthodes suivantes :
* hasSeries(password) qui renvoie True si le password contient une série de 3 lettres consécutives et False sinon.
* hasTwoPairs(password) qui renvoie True si le password contient deux paires non superposées de caractères et False sinon.
* hasNoBadChar(password) qui renvoie False si le password contient un caractère interdit, et True sinon.
Pendant ce temps là, comme il n'a pas trop confiance en Joséphine, il écrit les tests correspondants.
1.1 Prenez la place d'Edward et écrivez (au choix, en docstring ou en unittest) tous les tests pour vérifier le code de Josephine. Vérifiez que vos tests compilent et échouent (puisque vous n'avez pas encore le code).
1.2 COMMIT
1.3 Prenez maintenant la place de Joséphine et écrivez le code.
1.4 Jouez vos tests et itérez jusqu'à ce que tout aille bien.
1.5 COMMIT.
1.6 Écrivez maintenant de nouveaux tests pertinents pour getNext(password). Vérifiez que vos tests compilent.
1.7 Modifiez getNext(password) en utilisant les trois fonctions de Joséphine pour que la fonction ne renvoie que des mots de passe valide.
1.8 Une fois que tous vos tests seront valides, COMMIT.
1.9 Pour une chaîne de longueur N, combien de mots de passe valides peuvent être générés ? Comment le calculez-vous ? Répondez dans “reponse.txt”.
Exercice 2 (facultatif) : interface utilisateur.
Écrivez une nouvelle fonction qui demande à l'utilisateur de saisir un mot de passe et qui lui retourne le mot de passe suivant selon les règles mentionnées ci-dessus.
Vous devrez gérer les erreurs, et proposer les tests correspondants.
Partie 4. La vengeance d'Edward
Pour se venger de ce stupide administrateur système, Edward lui donne une énigme à résoudre. Il lui demande d'écrire un programme qui permet de trouver en moins de 4 secondes, sur sa machine, la longueur du 10ème successeur de n'importe quel terme passé en paramètre dans la suite de Conway.
Gunter, après avoir passé plusieurs heures à comprendre le problème (les énigmes, c'est pas son truc), écrit en deux temps trois mouvements l'algo qui répond au problème.
Furieux, Edward lui dit “bah puisque t'es si malin, donne moi donc le 50ème successeur de “1321131112”. Fier, Gunter modifie deux valeurs dans son code et l'exécute. 2h plus tard, son pauvre Core i7 mouline toujours…
Serez-vous plus malin que Gunter ?
- Écrire les tests puis la fonction getNext(terme) qui renvoie le successeur d'un terme de la suite de Conway.
- COMMIT
- Écrire les tests puis la fonction getNNext(n,terme) qui renvoie nième successeur d'un terme de la suite de Conway.
- COMMIT
- Mettez en place des tests de performances et faites varier votre implémentation pour voir si vous pouvez l'améliorer.
- Sous quelles conditions peut-on voir apparaître le chiffre 4 dans la suite de Conway ?
Ressources
- password.py
# coding: utf-8 def getNext(password): """ Série de tests exprimés en doctest >>> getNext('a') 'b' >>> getNext('az') 'ba' >>> getNext('bc') 'bd' """ pwd = list(password) #1 found = False i=len(pwd)-1 while not found: if pwd[i] < 'z': pwd[i] = chr(ord(pwd[i])+1) #2 found = True else: i = i-1 return ''.join(pwd) #3 # Grâce à ce fragment de code, si vous exécutez ce fichier, les tests doctests seront exécutés également. # Si vous ne voulez plus que les tests s'exécutent, commentez les deux lignes ci-dessous. # Si vous préférez lancer vos tests à la main, commentez également les lignes, et utilisez "python -m doctest pass.py" en console. if __name__ == "__main__": import doctest doctest.testmod()
- test_password.py
# coding: utf-8 import unittest import password as pwd class TestPassword(unittest.TestCase): def test_getNextNormal(self): self.assertEqual(pwd.getNext("abcd"), "abce") def test_getNextEndLine(self): self.assertEqual(pwd.getNext("abhz"), "abia") # Permet d'exécuter les tests si ce fichier est exécuté unittest.main()
Ce TP est fortement inspiré des exercices du prodigieux projet Advent of Code. Merci à son créateur pour ces belles sources d'inspiration.