logo

Prototyper une fonction

Jeux de tests

Généralités

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.

Retour sur notre exemple

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 :

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

Avec la création de modules

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.

Le module 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...