C7.3 : Tests

Généralités

L'utilisation de tests, en particulier pour les fonctions, est une étape importante. Elle permet de limiter les risques de bugs lors de l'écriture et lors de la mise à jour du code.

Il est en général souhaitable d'écrire une série de tests dès que les spécifications de la fonction sont établies, avant de coder.

Le choix des tests est important :

  • - les cas particuliers donnés dans la spécification de la fonction doivent être testés ;
  • - les cas limites des préconditions de la fonction doivent être testés ;
  • - les tests doivent êtres variés et prendre en compte l'ensemble des situations correspondant aux préconditions de la fonction.

Par exemple :

Les tests avec le module testmod

Rappels sur la syntaxe avec un exemple

La fonction mathématique factorielle(n) donne le produit des nombres de 1 à n. De plus factoriel(0) = 1.

Voici un programme avec :

# Définition des fonctions
def fact(n):
    """
    Calcul et renvoie factoriel de n.

    Paramètre n : (int) un entier positif
    Valeur renvoyée : (int) la factorielle de n.

    Exemples :
    >>> fact(3)
    6
    >>> fact(5)
    120
    """
    pass

# Programme principal
if __name__ == '__main__': # ligne utile si le fichier sert de module
    import doctest
    doctest.testmod(verbose = True)

A faire (sur feuille puis sur ordinateur) :

Afficher la correction
# Définition des fonctions
def fact(n):
    """
    Calcul et renvoie factoriel de n.

    Paramètre n : (int) un entier positif
    Valeur renvoyée : (int) la factorielle de n.

    Exemples :
    >>> fact(0)
    1
    >>> fact(1)
    1
    >>> fact(3)
    6
    >>> fact(5)
    120
    """
    if n == 0:
        return 1
    else: 
        f = 1
        for i in range(2, n+1):
            f = i * f
        return f

# Programme principal
if __name__ == '__main__': # ligne utile si le fichier sert de module
    import doctest
    doctest.testmod(verbose = True)

Applications

Application 1 (sur feuille)

On souhaite écrire une fonction qui prend une liste de nombres entiers en paramètres et renvoie la somme des nombres de cette liste.

1) Proposer une série de tests pour cette fonction.

2) Écrire le code de la fonction (y compris sa docstring avec les tests) et le programme principal pour valider les tests.

Application 2 (sur feuille puis sur ordinateur)

La fonction suivante est censée tester si la valeur v est présente dans la liste t.

def est_dans(v, t):
    i = 0
    while i < len(t) - 1 and t[i] != v:
        i = i + 1
    return  i < len(t)

1) Rédiger des tests pour cette fonction afin de montrer qu'elle est incorrecte.

2) Corriger la fonction.

Afficher la correction

1)

Fonction avec le test qui ne passe pas

def est_dans(v, t):
    """
    >>> est_dans(10, [5, 4, 8, 3])
    False
    """
    i = 0
    while i < len(t) - 1 and t[i] != v:
        i = i + 1
    return  i < len(t)

# Programme principal
import doctest
doctest.testmod(verbose = True)

2)

Fonction corrigée avec un jeu de test complet

def est_dans(v, t):
    """
    # Tests avec une liste de longueur 1
    >>> est_dans(5, [5])
    True
    >>> est_dans(8, [5])
    False
    
    # Tests avec une liste de longueur > 1
    >>> est_dans(5, [5, 4, 8, 3]) # avec la première valeur
    True
    >>> est_dans(8, [5, 4, 8, 3]) # avec une valeur du milieu
    True
    >>> est_dans(3, [5, 4, 8, 3]) # avec la dernière valeur
    True
    >>> est_dans(2, [5, 4, 8, 3]) # avec une valeur qui n'est pas dans la liste
    False

    """
    i = 0
    while i < len(t) and t[i] != v:
        i = i + 1
    return  i < len(t)

# Programme principal
import doctest
doctest.testmod(verbose = True)

Les tests avec les assertions

Présentation

Il est parfois nécessaire de procéder à des tests sans modifier une fonction (par exemple, si la fonction est dans un module à part ou si c'est une fonction récursive...). Dans ce cas, l'utilisation du module testmod n'est pas possible directement (remarque : elle pourrait se faire en englobant la fonction dans une autre fonction).

Une autre façon de faire des tests est d'utiliser les assertions.

Exemples de syntaxes

def fact(n):
    """
    Calcule et renvoie factoriel de n.
    Paramètre n : (int) un entier strictement positif
    Valeur renvoyée : (int) la factorielle de n
    """
    pass

# Programme principal
if __name__ == '__main__': # ligne utile si le fichier sert de module
    assert fact(0) == 1
    assert fact(1) == 1
    assert fact(4) == 4
    print("Tous les tests sont ok")

Applications

Application 1 (sur feuille)

On considère la fonction suivante :

def t(b):
    a = 0
    for i in range(len(b)):
        a += b[i]
    return a

1) Améliorer la lisibilité du code de cette fonction.

2) Écrire un programme principal avec une série de tests sous forme d'assertions.

Application 2 (sur feuille puis sur ordinateur)

On considère le programme ci-dessous :

def somme(n):
    '''
    @param n : (int) un entier positif ou nul.
    '''
    s = 0
    for i in range(0,n+1):
        s = s + i
    return s

# === Partie principale ===
def est_correcte():
    from random import randint
    liste = [randint(0,100) for _ in range(21)]
    for n in liste:
        assert somme(n) == n*(n+1)/2
    return True

if est_correcte():
    print("La fonction donne bien le résultat attendu !")

1) Que fait la fonction somme ? Compléter la docstring de cette fonction.

2) Que fait la "partie principale" du programme ? Ajouter des commentaires au code.

3) Modifier le programme pour que la valeur limite 0 soit systématiquement testée.

Afficher la correction

1)

Voir le code ci-dessous

2)

Voir le code ci-dessous

3)

La partie principale du programme permet de tester la fonction somme avec un ensemble de 21 valeurs entières positives tirées aléatoirement.

 

 

def somme(n):
    '''
    Renvoie la somme des nombres de 1 au nombre passé en paramètre.
    @param n : (int) un entier positif ou nul.
    '''
    s = 0
    for i in range(0,n+1):
        s = s + i
    return s

# === Partie principale ===
def est_correcte_v1(): # version où 0 est ajouté à la liste pour être testé systématiquement
    from random import randint
    # Création d'une liste de 21 nombres aléatoires
    liste = [randint(1,100) for _ in range(21)] + [0]
    # Test de la fonction somme avec chacun des nombres de la liste
    # en commparant le résultat de somme(n) à la formule n*(n+1)/2
    for n in liste:
        assert somme(n) == n*(n+1)/2
    return True

def est_correcte_v2(): # vesion où 0 est testé en plus
    from random import randint
    # Création d'une liste de 21 nombres aléatoires
    liste = [randint(1,100) for _ in range(21)]
    # Test de la fonction somme avec chacun des nombres de la liste
    # en commparant le résultat de somme(n) à la formule n*(n+1)/2
    assert somme(0) == 0
    for n in liste:
        assert somme(n) == n*(n+1)/2
    return True

if est_correcte_v1():
    print("La fonction donne bien le résultat attendu !")

TD : Une fonction qui teste une fonction de tri d'une liste

On considère une fonction tri(liste) qui prend une liste d'entiers en paramètre et trie par ordre croissant les éléments de cette liste (le code de cette fonction n'est pas utile pour la suite).

On souhaite écrire une fonction qui automatise le test de la fonction tri().

1) Écrire la fonction est_triee(liste) qui renvoie True si la liste passée en paramètre est triée dans l'ordre croissant ou False dans le cas contraire.

2*) Écrire une fonction ont_meme_contenu(liste1, liste2) qui renvoie True si les deux listes passées en paramètres ont exactement les mêmes éléments (pas forcément dans le même ordre) ou False dans le cas contraire. On n'utilisera pas de fonction de tri pour cela.

3) Ecrire le code la la fonction test_fonc_tri() qui teste la fonction tri. Cette fonction doit :

Afficher la correction
from random import randint

def tri(liste):
    liste.sort() # Un code a été mis pour pouvoir tester la fonction test_fct_tri

def est_triee(liste):
    """
    Teste si une liste est triée par ordre croissant

    Exemples :
    >>> est_triee([1, 2, 3])
    True
    >>> est_triee([3, 2, 1])
    False
    """
    test_tri = True
    i = 0
    while i < len(liste)-1:
        if liste[i] > liste[i+1]:
            test_tri = False
            i = len(liste)
        else:
            i = i + 1
    return test_tri

def ont_meme_contenu_v1(liste1, liste2):
    """
    Teste si deux listes ont les mêmes éléments

    Exemples :
    >>> ont_meme_contenu_v1([], [])
    True
    >>> ont_meme_contenu_v1([1], [1])
    True
    >>> ont_meme_contenu_v1([1, 2], [1])
    False
    >>> ont_meme_contenu_v1([1], [1, 2])
    False
    >>> ont_meme_contenu_v1([1, 1], [1])
    False
    >>> ont_meme_contenu_v1([1], [1, 1])
    False
    """
    copie_liste2 = list(liste2)
    for elt in liste1:
        if elt in copie_liste2:
            copie_liste2.remove(elt)
        else:
            return False
    if copie_liste2 == []:
        return True
    else:
        return False

def ont_meme_contenu_v2(liste1, liste2):
    """
    Teste si deux listes ont les mêmes éléments
    """
    # Construction du dictionnaire d’occurrences pour liste1
    dico1 = {}
    for elt in liste1:
        if elt in dico1:
            dico1[elt] = dico1[elt] + 1
        else:
            dico1[elt] = 1
    # Construction du dictionnaire d'occurrences pour liste2
    dico2 = {}
    for elt in liste2:
        if elt in dico2:
            dico2[elt] = dico2[elt] + 1
        else:
            dico2[elt] = 1
    # Vérification de l'égalité des deux dictionnaires
    if dico1 == dico2:
        return True
    else:
        return False

def teste_fct_tri():
    # Création d'une liste de nombres aléatoires
    liste = [randint(0,99) for _ in range(0,10)]
    print(liste)
    # Création d'une copie triée de la liste
    liste_triee = list(liste)
    tri(liste_triee)
    print(liste_triee)
    # Vérification que la liste est triée
    assert est_triee(liste_triee), "La liste n'est pas triée !"
    # Vérification que la liste triée contient les même valeurs que la liste initiale
    assert ont_meme_contenu_v1(liste, liste_triee), "La liste triée diffère de la liste initiale"
    return True

# Programme principal
if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose = True)