Essentiels des tests en Python

Beginner

This tutorial is from open-source community. Access the source code

Introduction

La nature dynamique de Python rend les tests d'une importance cruciale pour la plupart des applications. Il n'y a pas de compilateur pour détecter vos bugs. Le seul moyen de trouver des bugs est d'exécuter le code et de s'assurer d'avoir essayé toutes ses fonctionnalités.

Assertions

L'instruction assert est une vérification interne pour le programme. Si une expression n'est pas vraie, elle lève une exception AssertionError.

Syntaxe de l'instruction assert.

assert <expression> [, 'Message de diagnostic']

Par exemple.

assert isinstance(10, int), 'Attendu un int'

Elle ne devrait pas être utilisée pour vérifier les entrées de l'utilisateur (c'est-à-dire les données saisies dans un formulaire web ou autre chose). Son but est plutôt de vérifications internes et d'invariants (conditions qui devraient toujours être vraies).

Programmation par contrat

Connue également sous le nom de Conception par Contrat, l'utilisation extensive d'assertions est une approche pour concevoir des logiciels. Elle stipule que les concepteurs de logiciels devraient définir des spécifications d'interface précises pour les composants du logiciel.

Par exemple, vous pourriez placer des assertions sur toutes les entrées d'une fonction.

def add(x, y):
    assert isinstance(x, int), 'Expected int'
    assert isinstance(y, int), 'Expected int'
    return x + y

Vérifier les entrées permettra immédiatement de détecter les appelants qui n'utilisent pas les arguments appropriés.

>>> add(2, 3)
5
>>> add('2', '3')
Traceback (most recent call last):
...
AssertionError: Expected int
>>>

Tests en ligne

Les assertions peuvent également être utilisées pour effectuer des tests simples.

def add(x, y):
    return x + y

assert add(2,2) == 4

De cette manière, vous incluez le test dans le même module que votre code.

Avantage : Si le code est manifestement cassé, les tentatives d'importer le module provoqueront une erreur.

Cela n'est pas recommandé pour les tests exhaustifs. C'est plutôt un "test de fumée" de base. La fonction fonctionne-t-elle avec n'importe quel exemple? Si ce n'est pas le cas, alors quelque chose est certainement cassé.

Module unittest

Supposons que vous ayez du code dans simple.py.

## simple.py

def add(x, y):
    return x + y

Maintenant, supposons que vous vouliez le tester. Créez un fichier de test séparé comme ceci dans /home/labex/project/test_simple.py.

## test_simple.py

import simple
import unittest

Ensuite, définissez une classe de test.

## test_simple.py

import simple
import unittest

## Remarquez qu'elle hérite de unittest.TestCase
class TestAdd(unittest.TestCase):
  ...

La classe de test doit hériter de unittest.TestCase.

Dans la classe de test, vous définissez les méthodes de test.

## test_simple.py

import simple
import unittest

## Remarquez qu'elle hérite de unittest.TestCase
class TestAdd(unittest.TestCase):
    def test_simple(self):
        ## Test avec des arguments entiers simples
        r = simple.add(2, 2)
        self.assertEqual(r, 5)
    def test_str(self):
        ## Test avec des chaînes de caractères
        r = simple.add('hello', 'world')
        self.assertEqual(r, 'helloworld')

*Important : Chaque méthode doit commencer par test.

Utilisation de unittest

Il existe plusieurs assertions intégrées dans unittest. Chacune d'entre elles affirme une chose différente.

## Affirmer que expr est vraie
self.assertTrue(expr)

## Affirmer que x == y
self.assertEqual(x,y)

## Affirmer que x!= y
self.assertNotEqual(x,y)

## Affirmer que x est proche de y
self.assertAlmostEqual(x,y,places)

## Affirmer que callable(arg1,arg2,...) lève exc
self.assertRaises(exc, callable, arg1, arg2,...)

Ce n'est pas une liste exhaustive. Il y a d'autres assertions dans le module.

Exécution de unittest

Pour exécuter les tests, convertissez le code en un script.

## test_simple.py

...

if __name__ == '__main__':
    unittest.main()

Ensuite, exécutez Python sur le fichier de test.

$ python3 test_simple.py
F.
========================================================
ÉCHEC: test_simple (__main__.TestAdd)
--------------------------------------------------------
Traceback (most recent call last):
  File "testsimple.py", line 8, in test_simple
    self.assertEqual(r, 5)
AssertionError: 4!= 5
--------------------------------------------------------
Exécuté 2 tests en 0.000s
ÉCHEC (échecs=1)

Commentaire

L'écriture d'un bons tests unitaires est un art et peut devenir assez complexe pour les grandes applications.

Le module unittest dispose d'un grand nombre d'options relatives aux exécuteurs de tests, à la collecte des résultats et à d'autres aspects des tests. Consultez la documentation pour plus de détails.

Outils de test tiers

Le module unittest intégré a l'avantage d'être disponible partout - c'est une partie de Python. Cependant, de nombreux programmeurs le trouvent également assez verbeux. Une alternative populaire est pytest. Avec pytest, votre fichier de test se simplifie comme suit :

## test_simple.py
import simple

def test_simple():
    assert simple.add(2,2) == 4

def test_str():
    assert simple.add('hello','world') == 'helloworld'

Pour exécuter un test, il suffit de taper une commande comme python -m pytest. Il trouvera et exécutera ensuite tous les tests. Le module peut être installé à l'aide de pip install pytest.

Il y a bien plus à pytest que cet exemple, mais il est généralement assez facile de commencer si vous décidez d'essayer.

Dans cet exercice, vous explorerez les mécanismes de base de l'utilisation du module unittest de Python.

Dans les exercices précédents, vous avez écrit un fichier stock.py qui contenait une classe Stock. Pour cet exercice, on suppose que vous utilisez le code écrit pour l'Exercice 7.9 relatif aux propriétés typées. Si, pour une raison quelconque, cela ne fonctionne pas, vous pouvez vouloir copier la solution de Solutions/7_9 dans votre répertoire de travail.

Exercice 8.1 : Écrire des tests unitaires

Dans un fichier séparé test_stock.py, écrivez un ensemble de tests unitaires pour la classe Stock. Pour vous aider à commencer, voici un petit fragment de code qui teste la création d'instances :

## test_stock.py

import unittest
import stock

class TestStock(unittest.TestCase):
    def test_create(self):
        s = stock.Stock('GOOG', 100, 490.1)
        self.assertEqual(s.name, 'GOOG')
        self.assertEqual(s.shares, 100)
        self.assertEqual(s.price, 490.1)

if __name__ == '__main__':
    unittest.main()

Exécutez vos tests unitaires. Vous devriez obtenir une sortie qui ressemble à ceci :

.

Ran 1 tests in 0.000s

OK

Une fois que vous êtes satisfait que cela fonctionne, écrivez d'autres tests unitaires qui vérifient les points suivants :

  • Vérifiez que la propriété s.cost renvoie la valeur correcte (49010.0)
  • Vérifiez que la méthode s.sell() fonctionne correctement. Elle devrait décrémenter la valeur de s.shares en conséquence.
  • Vérifiez que l'attribut s.shares ne peut pas être défini sur une valeur non entière.

Pour la dernière partie, vous devrez vérifier qu'une exception est levée. Un moyen simple de le faire est avec du code comme ceci :

class TestStock(unittest.TestCase):
  ...
    def test_bad_shares(self):
         s = stock.Stock('GOOG', 100, 490.1)
         with self.assertRaises(TypeError):
             s.shares = '100'

Sommaire

Félicitations! Vous avez terminé le laboratoire sur les tests. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.