Aller au contenu

Tester

Toute fonction (et tout programme) doit être testée.

Des tests nombreux et bien choisis ne garantiront jamais que le programme est exempt de bugs mais permettent de s'assurer que la fonction n'est pas trivialement fausse et qu'elle semble fonctionner dans les cas usuels.

Si les tests ne sont jamais une preuve de l'absence de bugs, vous devrez penser la construction de vos tests de façon à ce qu'ils débusquent les bugs, en pensant notamment à tous les cas particuliers qu'un premier raisonnement générique aurait pu oublier.

Exemple

On veut écrire une fonction spécifiée ainsi:

def pepg(m):
    """
    m -- entier naturel

    renvoie le plus petit entier naturel n tel que 2**n > m.
    """

Cassien propose le code suivant:

def pepg(m):
    """
    m -- entier naturel

    renvoie le plus petit entier naturel n tel que 2**n > m.
    """
    p = 1
    n = 0
    while p < m:
        p = p*2
        n = n+1
    return n 

Il teste ensuite sa fonction avec l'argument 3:

>>> pepg(3)
2

et estime que son code est correct puisque 22 > 3 et que 21 ≤ 3.

Qu'en pensez-vous?

Aide: un test qui échoue.

Quelques tests supplémentaires montrent que ce code n'est pas correct.

Lorsqu'on écrit une fonction, il faut toujours tester les cas limites.

Ici, on doit notamment penser à ce qui peut se passer:

  • pour les premiers entiers (0, 1, 2).
  • pour tous les cas où il y a égalité dans les comparaisons.

On constate notamment ici que la fonction ne satisfait pas sa spécification lorsque m est une puissance de deux (c'est un cas où p prendra exactement la valeur de m lors de l'une des itérations). On obtient en effet:

>>> pepg(4)
2
>>> pepg(8)
3

alors que l'on n'a pas 2^2 > 4, ni 2^3 > 8.

Essayez de trouver comment corriger le code avant de lire la solution.

Correction du code

Le test while p < m doit être corrigé en while p <= m.

def pepg(m):
    """
    m -- entier naturel

    renvoie le plus petit entier naturel n tel que 2^n > m.
    """
    p = 1
    n = 0
    while p <= m:
        p = p*2
        n = n+1
    return n 

Tests de documentation

Il vous sera demandé en cours de NSI de renseigner systématiquement le docstring de vos fonctions et de le compléter par quelques tests et les résultats attendus, en suivant la syntaxe donnée en exemple ci-dessous.

Les tests couvriront notamment les cas:

  • cas génériques.
  • cas extrêmes.

Ici, on pourrait par exemple compléter comme suit (des cas "génériques" avec 3, 5; des cas extrêmes: les premiers entiers naturels: 0, 1; des cas avec égalité dans les tests de comparaison: puissances de deux 1, 2, 4).

def pepg(m):
    """
    m -- entier naturel

    renvoie le plus petit entier naturel n tel que 2^n > m.
    >>> pepg(0)
    0
    >>> pepg(1)
    1
    >>> pepg(2)
    2
    >>> pepg(3)
    2
    >>> pepg(4)
    3
    >>> pepg(5)
    3
    """
    p = 1
    n = 0
    while p <= m:
        p = p*2
        n = n+1
    return n 
Remarque: la syntaxe des tests de documentation

Le choix de la présentation

>>> pepg(0)
0
>>> pepg(1)
1
>>> pepg(2)
2
>>> pepg(3)
2
>>> pepg(4)
3
>>> pepg(5)
3
n'est pas arbitraire. Il s'agit d'une copie de ce qu'on vous obtenez dans une console lorsque vous testez votre fonction. Ci-dessous, vous voyez par exemple un fichier .py ouvert dans geany et des appels à la fonction dans la console.

Par ailleurs, nous verrons ci-dessous que ces tests de la chaîne de documentation peuvent être utilisés directement par le module doctest. Et en fait le module doctest se contente de comparer ces tests avec ce que l'on obtiendrait effectivement dans une console. La syntaxe à respecter pour ces tests sera donc guidée par ce que l'on attend comme affichages en console.

Lancer les tests de documentation depuis la console

Cette convention des tests dans le docstring permet de mieux comprendre la spécification pour le lecteur.
Pour le programmeur, qui pensera ces tests avant de programmer, elle est aussi un guide pour ne pas oublier de cas.

Enfin, elle permet aussi des tests directs grâce au module doctest.

Enregistrons par exemple le script ci-dessus (en réintroduisant l'erreur sur la condition d'arrêt du while) dans un fichier nommé essai.py.

Contenu du fichier essai.py:

def pepg(m):
    """
    m -- entier naturel

    renvoie le plus petit entier naturel n tel que 2^n > m.
    >>> pepg(0)
    0
    >>> pepg(1)
    1
    >>> pepg(2)
    2
    >>> pepg(3)
    2
    >>> pepg(4)
    3
    >>> pepg(5)
    3
    """
    p = 1
    n = 0
    while p < m:
        p = p*2
        n = n+1
    return n 

Ouvrons maintenant une console dans le répertoire de notre fichier et entrons la commande suivante:

$ python3 -m doctest -v essai.py

(rappel: n'entrez pas le $, il s'agit de l'invite de commande normalement déjà présente).

Vous devriez obtenir la liste des tests réussis et des tests échoués:

Trying:
    pepg(0)
Expecting:
    0
ok
Trying:
    pepg(1)
Expecting:
    1
**********************************************************************
File "/home/jmm/Documents/NSI/coursNSI/for-if-while/docs/essai.py", line 8, in essai.pepg
Failed example:
    pepg(1)
Expected:
    1
Got:
    0
Trying:
    pepg(2)
Expecting:
    2
**********************************************************************
File "/home/jmm/Documents/NSI/coursNSI/for-if-while/docs/essai.py", line 10, in essai.pepg
Failed example:
    pepg(2)
Expected:
    2
Got:
    1
Trying:
    pepg(3)
Expecting:
    2
ok
Trying:
    pepg(4)
Expecting:
    3
**********************************************************************
File "/home/jmm/Documents/NSI/coursNSI/for-if-while/docs/essai.py", line 14, in essai.pepg
Failed example:
    pepg(4)
Expected:
    3
Got:
    2
Trying:
    pepg(5)
Expecting:
    3
ok
1 items had no tests:
    essai
**********************************************************************
1 items had failures:
   3 of   6 in essai.pepg
6 tests in 2 items.
3 passed and 3 failed.
***Test Failed*** 3 failures.

Le module doctest repère les tests grâce à la présence des chevrons >>>, il est donc impératif de respecter cette syntaxe.

En supprimant l'option -v:

python3 -m doctest essai.py

on n'obtient que la liste des échecs (et donc aucun affichage si tous les tests réussissent).

Pour en savoir plus sur le module doctest, consulter la documentation python.

Lancer les tests de documentation avec le fichier .py

Nous avons vu dans le cadre précédent comment lancer les tests de la chaîne de documentation depuis une console.

On peut également intégrer directement dans son fichier .py de quoi lancer les tests.

Reprenons l'exemple ci-dessus en lui ajoutant une fonction permettant de lancer les tests doctest.

def test_pepg():
    """
    fonction lançant les tests de la chaîne de documentation
    de la fonction pepg.
    """
    import doctest
    doctest.testmod(verbose=True)

def pepg(m):
    """
    m -- entier naturel

    renvoie le plus petit entier naturel n tel que 2^n > m.
    >>> pepg(0)
    0
    >>> pepg(1)
    1
    >>> pepg(2)
    2
    >>> pepg(3)
    2
    >>> pepg(4)
    3
    >>> pepg(5)
    3
    """
    p = 1
    n = 0
    while p <= m:
        p = p*2
        n = n+1
    return n 


test_pepg()
assert

Vous pourrez dans la suite définir vos tests avec assert si vous préférez: le seul point incontournable est de prévoir un jeu de tests.

On pourrait par exemple prévoir la fonction de tests suivante:

def test_pepg():
    """
    fonction lançant des tests  
    sur la fonction pepg 
    """
    assert pepg(0) == 0
    assert pepg(1) == 1
    assert pepg(2) == 2
    assert pepg(3) == 2
    assert pepg(4) == 3
    assert pepg(5) == 3


def pepg(m):
    """
    m -- entier naturel

    renvoie le plus petit entier naturel n tel que 2^n > m.
    >>> pepg(0)
    0
    >>> pepg(1)
    1
    >>> pepg(2)
    2
    >>> pepg(3)
    2
    >>> pepg(4)
    3
    >>> pepg(5)
    3
    """
    p = 1
    n = 0
    while p < m:
        p = p*2
        n = n+1
    return n 

et la lancer, par exemple, dans le terminal:

>>> test_pepg()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "a.py", line 7, in test_pepg
    assert pepg(1) == 1
AssertionError

On constate une erreur d'assertion AssertionError (puisqu'on a exécuté ici avec < au lieu de <= dans la comparaison)

Une fonction de tests

L'utilisation d'une fonction dédiée aux tests permet parfois de compléter les tests de la chaîne de documentation par d'autres tests (notamment lorsque ces tests demandent un texte conséquent et qu'ils surchargeraient trop la chaîne de documentation).

Exemple

Dans l'exemple ci-dessous, la fonction de tests contient une assertion en plus de lancer les tests de la chaîne de documentation de la fonction à tester.

Cette assertion ne provoquera aucun affichage si elle est satisfaite.

  1. Lancez le script tel quel. Vous ne devriez n'avoir ici aucun affichage car tous les tests passent.
  2. Lancez ensuite le script en remplaçant la dernière ligne par test_pepg(True). Vous devriez obtenir un compte-rendu des tests de la chaîne de documentation.
  3. Enfin, remplacez l'assertion de la fonction de tests par une assertion incorrecte (par exemple assert pepg(8) == 100). Vous devriez obtenir une "AssertionError".
def test_pepg(bavard = False):
    """
    fonction lançant les tests de la chaîne de documentation
    de la fonction pepg.
    """
    assert pepg(8) == 4
    import doctest
    doctest.testmod(verbose=bavard)

def pepg(m):
    """
    m -- entier naturel

    renvoie le plus petit entier naturel n tel que 2^n > m.
    >>> pepg(0)
    0
    >>> pepg(1)
    1
    >>> pepg(2)
    2
    >>> pepg(3)
    2
    >>> pepg(4)
    3
    >>> pepg(5)
    3
    """
    p = 1
    n = 0
    while p <= m:
        p = p*2
        n = n+1
    return n 


test_pepg()
Remarque: les métiers du test

Vous aurez noter une petite difficulté pour un débutant: la fonction de tests doit elle-même être dépourvue d'erreurs!
Tester du logiciel est en fait devenu aujourd'hui un métier à part entière, cela demande des compétences poussées et un travail qui n'est jamais négligeable et qui ne doit jamais être négligé dans les phases de développement logiciel.

Remarque: les modules de tests

L'importance des tests fait qu'il existe dans quasiment tous les langages des modules dédiés aux tests. En python, nous avons déjà cité doctest, il existe aussi unittest et pytest. Ces modules demandent à ce que les fonctions de tests respectent certaines règles d'écriture... En contrepartie, ils permettent d'automatiser les tests, de repèrer les erreurs, etc... Vous pouvez par exemple lire cette page sur pytest.

Assertion

Un autre risque d'erreur est lié à l'appel des fonctions avec un argument qui n'est pas du type attendu.

On peut ajouter des assertions pour vérifier le respect des préconditions sur les paramètres lors des appels à la fonction et ainsi anticiper ce type d'erreur.

Avec notre fonction précédente, on peut par exemple ajouter un assert pour s'assurer que la fonction est appelée avec un m entier positif ou nul.

On utilise ici isinstance pour tester le type de m.

(on a supprimé la chaîne de documentation pour allèger... mais ce n'est pas un exemple à suivre ! les chaînes de documentation devront toujours être présentes !)

def pepg(m):
    assert isinstance(m, int), "Attention, le paramètre doit être entier."
    assert m >= 0, "Attention, le paramètre doit être positif ou nul."
    p = 1
    n = 0
    while p <= m:
        p = p*2
        n = n+1
    return n 

Testez cette fonction

  • avec un entier positif,
  • avec un entier négatif,
  • avec un flottant

et observez les réponses obtenues.

Solution