Skip to content

Chp I. Programmation par les tests

On va mettre en avant dans ce petit chapitre les bonnes pratiques pour produire un code fiable. On parle aujourd'hui de Test Driven Development (TDD) que l'on peut traduire par développement piloté par les tests. Cette méthode est inspiré du Test First Design. Le principe, comme son nom le suggère est d'écrire les tests avant d'écrire le code proprement dit. En python on utilisera le mot clef assert. On restera très superficiel (pas de génération automatique de jeux de tests par exemple...), l'idée étant juste de prendre les bonnes manières !

Warning

Attention, il n'est pas question ici de démonstration de code, mais plutôt d'utiliser une méthode permettant simplement d'estimer, ou encore de se convaincre que notre code est correct !

1. Découverte et utilisation du mot clé assert

assert est une commande python qui permet de stopper le déroulement d'un programme (en levant une exception) si une condition de notre choix n'est pas vérifiée.

Une première utilisation de assert est par exemple pour s'assurer de la validité des valeurs reçues par une fonction.

Voyons cela sur un exemple simple.

Consolidation de la fonction factorielle

Rappel mathématique : si \(n\) est un nombre entier positif, on note \(n!\) le nombre obtenu en faisant le produit de tous les entiers entre 1 et n. Ce nombre s'appelle la factorielle de \(n\). Par définition, on pose de plus \(0!=1\).

Un premier code qui pose problème : on propose le code d'une fonction factorielle(n :int)->int qui calcule la factorielle d'un nombre.

# Une fonction factorielle avec un code qui pose problème pour les nombres négatifs :
def factorielle(n : int)-> int :
    """ reçoit un entier positif et renvoie sa factorielle"""
    prod = 1
    for i in range(2,n+1):
        prod = prod * i
    return prod

Ce code n'est pas robuste car la fonction proposée ne renvoie pas toujours le bon résultat. En effet, si on l'appelle avec un nombre négatif elle renvoie 1 au lieu d'une erreur.

Amélioration du code : on peut améliorer cette situation grâce au mot clé assert proposé par le langage python. Voici comment:

# Amélioration du code avec l'utilisation du mot clé "assert" pour lever une erreur si besoin
def factorielle(n : int)-> int :
    """ reçoit un entier positif et renvoie
    sa factorielle"""
    assert n >= 0 # lève une 'exception' (erreur) si n<0   
    prod = 1
    for i in range(2,n+1):
        prod = prod * i
    return prod
Cette fois-ci, si on exécute factorielle(-2) le script stoppera et renverra une erreur.

Voyons dans la prochaine section comment tirer parti de cette fonctionnalité (mot clé assert) pour mettre en place la technique du « test first design » en créant des jeux de tests...

2. Programmation pilotée par les tests

Lors de la conception d'un programme, il est une bonne pratique de concevoir pour chaque fonction écrite, un jeu de tests qui permet d'augmenter les chances que celle-ci soit correcte. Voyons cela sur un exemple très simple :

Example

Imaginons que l'on doive écrire une fonction dont la signature est la suivante :

def double(x : float) -> float :
    """ Cette fonction reçoit un nombre flottant et renvoie le double de celui-ci """
    ..........
1. La première chose à faire est de créer une fonction de test, nommée test_double() dont le but est d'effectuer des tests.

Attention, le nom de la fonction test sera toujours test_ suivi du nom de la fonction à tester, et elle ne doit pas prendre de paramètre (parenthèses vides).

Cela n'est pas fondamental à notre niveau, mais c'est une bonne habitude, car c'est ainsi que l'on pourra automatiser des batteries de tests en utilisant des logiciels tiers conçus exprès.

Le code de la fonction test pourrait être quelque chose du genre :

def test_double():
    assert double(0) == 0
    assert double(17) == 34
    assert double(1.2) == 2.4
    assert double(-2) == -4
    print("Tous les tests sont passés")

On effectue quelques tests classiques (en essayant de penser à tester aussi des valeurs particulières) et on finit par un message nous informant que tous les tests sont passés lorsqu'il n'y a pas eu d'erreur.

2. Ensuite on écrit le code de la fonction double...

3. Enfin on le teste directement en exécutant la fonction test_double().

L'intérêt de procéder ainsi est qu'il est facile de modifier une fonction et de voir si elle passe toujours les tests.

Remarque : si l'on connait une fonction que l'on sait être juste et qui fait la même chose que celle que l'on souhaite écrire (par exemple on a déjà une fonction qui est correcte, et on souhaite la réécrire pour l'améliorer...), on peut imaginer faire d'un coup de nombreux tests en utilisant des boucles (on évitera d'utiliser le hasard, car les tests ne seraient dans ce cas pas reproductibles, et l'erreur éventuelle pourrait ne pas se produire à chaque fois...). Ici cela donnerait par exemple :

def test_double():
    for i in range(-100,100) :
        assert double(i) == 2*i
        print("tous les tests sont passés")

3. Les TDs