Le développement piloté par les tests ou TDD (pour Test Driven Development) est une méthode d’écriture de programme qui met en avant le fait d’écrire d’abord un test pour chaque spécification du programme puis écrire le code qui permettra au programme de passer ce test avec succès. Cette manière de procéder permet en général de penser en premier lieu aux spécifications voulues, ce qui améliore la structure générale du code produit et permet de s’assurer que l’on dispose de toute une batterie de tests sous la main.
Plus particulièrement, le TDD a été théorisé sous forme de trois lois.
Bien qu’il soit exclu à notre niveau d’avoir un environnement de développement complet qui soit TDD-compatible, le fait d’écrire a priori des tests qui sont supposés être réussis par les fonctions que l’on veut coder oblige à penser à tous les cas particuliers que l’on peut être amené à croiser dans le problème que l’on tente de résoudre.
On se propose de tester l’algorithme qui permet de convertir un décimale en un binaire sur un nombre de bits. L'algorithme renvoit une chaine de caractère représentant ce nombre binaire.
L’idée la plus simple pour mettre en place un jeu de tests est d’utiliser la commande assert
.
Elle permet de vérifier que les résultats voulus correspondent à
ce que renvoie la fonction désirée. En particulier :
0
s'écrit en binaire sur 4
bits 0000
.
3
s'écrit en binaire sur 4
bits 0011
.
127
s'écrit en binaire sur 7
bits 1111111
.
128
s'écrit en binaire sur 8
bits 10000000
.
315
s'écrit en binaire sur 10
bits 0100111011
.
Voici le code Python incluant ce jeu de tests :
def dec_vers_bin(n,bits):
'''convertit le nombre n en binaire sur un nombre de bits'''
#pré-conditions
assert type(n)==int and n>=0, 'n doit être un entier natuel'
assert type(bits)==int and bits>=0, 'bits doit être un entier natuel'
assert bits!=0 and n<2**(bits), 'le nombre de bits est insuffisant'
binaire=""
for a in range(bits):
binaire=str(n%2)+binaire
n=n//2
#post-conditions
assert max(binaire)=='0' or max(binaire)=='1',"pas un nb binaire"
assert len(binaire)==bits,"le nombre binaire n'a pas le nombre de bits voulu"
return binaire
#jeu de tests
assert dec_vers_bin(0,4)=='0000'
assert dec_vers_bin(3,4)=='0011'
assert dec_vers_bin(127,7)=='1111111'
assert dec_vers_bin(128,8)=='10000000'
assert dec_vers_bin(315,10)=='0100111011'
Si un test échoue, il y aura un message d'erreur du type :
Traceback (most recent call last):
File "C:\Users\prof\Desktop\python\code.py", line 20, in <module>
assert dec_vers_bin(3,4)=='0011'
AssertionError
Le fichier code.py du module contenant la définition de notre fonction (appelée sans grande originalité dec_vers_bin) peut alors être écrit sous la forme suivante, où l’on utilise la construction
if __name__ == '__main__':
qui permet de n’exécuter les tests que lorsqu’on appelle Python directement sur le module et non pas quand celui-ci est inclus dans
un programme plus large.
Le code en Python sera donc :
def dec_vers_bin(n,bits):
'''convertit le nombre n en binaire sur un nombre de bits'''
#pré-conditions
assert type(n)==int and n>=0, 'n doit être un entier natuel'
assert type(bits)==int and bits>=0, 'bits doit être un entier natuel'
assert bits!=0 and n<2**(bits), 'le nombre de bits est insuffisant'
binaire=""
for a in range(bits):
binaire=str(n%2)+binaire
n=n//2
#post-conditions
assert max(binaire)=='0' or max(binaire)=='1',"pas un nb binaire"
assert len(binaire)==bits,"le nombre binaire n'a pas le nombre de bits voulu"
return binaire
if __name__ == '__main__':
assert dec_vers_bin(0,4)=='0000'
assert dec_vers_bin(3,4)=='0011'
assert dec_vers_bin(127,7)=='1111111'
assert dec_vers_bin(128,8)=='10000000'
assert dec_vers_bin(315,10)=='0100111011'
Rajouter des tests (même faux) pour voir ce qui se passe.
doctest
Le module doctest
permet d'inclure le jeu de tests à l'intérieur du corps de la fonction
Voici le nouveau code Python :
def dec_vers_bin(n,bits):
'''convertit le nombre n en binaire sur un nombre de bits
jeu de tests:
>>> dec_vers_bin(0,4)
'0000'
>>> dec_vers_bin(3,4)
'0011'
>>> dec_vers_bin(127,7)
'1111111'
>>> dec_vers_bin(128,8)
'10000000'
>>> dec_vers_bin(315,10)
'0100111011'
'''
#pré-conditions
assert type(n)==int and n>=0, 'n doit être un entier natuel'
assert type(bits)==int and bits>=0, 'bits doit être un entier natuel'
assert bits!=0 and n<2**(bits), 'le nombre de bits est insuffisant'
binaire=""
for a in range(bits):
binaire=str(n%2)+binaire
n=n//2
#post-conditions
assert max(binaire)=='0' or max(binaire)=='1',"pas un nb binaire"
assert len(binaire)==bits,"le nombre binaire n'a pas le nombre de bits voulu"
return binaire
# A la fin de votre script, mettez ce code qui va activer les doctests
if __name__ == '__main__':
import doctest
doctest.testmod(verbose=True)
Remarque : l'argument verbose=True
permet d'afficher le test même s'il est réussi. Si on veut afficher que les tests qui échouent on mettra verbose=False
.
Le programme affichera ce message :
Trying:
dec_vers_bin(0,4)
Expecting:
'0000'
ok
Trying:
dec_vers_bin(3,4)
Expecting:
'0011'
ok
Trying:
dec_vers_bin(127,7)
Expecting:
'1111111'
ok
Trying:
dec_vers_bin(128,8)
Expecting:
'10000000'
ok
Trying:
dec_vers_bin(315,10)
Expecting:
'0100111011'
ok
1 items had no tests:
__main__
1 items passed all tests:
5 tests in __main__.dec_vers_bin
5 tests in 2 items.
5 passed and 0 failed.
Test passed.
Malheureusement, aucun jeu de test ne peut garantir la justesse d'un programme, à moins de tout tester...