Les tests unitaires
Les Tests
Pourquoi tester ?
- Crash test de voiture
- Test de conformité
- Test qualité en industrie
- Test statistique
En informatique
- Pour vérifier que votre programme fonctionne
- Pour détecter des erreurs
- Pour éviter les régressions (quand vous modifiez du code)
Aucun test n’est parfait, mais cela permet quand même d’écarter de nombreuses erreurs.
Déjà tester une fonctionnalité d’un dev dont le cas nominal plante.
Définition
Un test ressemble à une expérience scientifique.
Il examine une hypothèse exprimée en fonction de trois éléments :
- les données en entrée
- l’objet à tester
- les résultats attendus
Cet examen est effectué sous conditions contrôlées pour pouvoir tirer des conclusions et, dans l’idéal, être reproduit.
Couverture de test
- Pourcentage de fonctions testées
- Indicateur de qualité
- Tendance, plutôt qu’une valeur fiable
Préférez faire peu de tests, mais des tests utiles !
Nombre de fonctions testées sur le nombre total de fonctions.
Tendance car facile de tester toutes les méthodes élémentaires pour augmenter mécaniquement sa couverture et de mettre de côté les tests sur les méthodes plus compliquées.
Les types de tests
Il existe de très nombreux tests différents, voici les principaux :
- Test unitaire : teste une fonction pour vérifier son bon fonctionnement
- Test fonctionnel : teste les cas d’utilisation du logiciel
- Test de charge : évaluent la capacité d’un système à gérer un volume élevé de données ou de transactions
- Test d’intégration : vérifie que différents composants ou modules d’un système interagissent correctement ensemble
- Test d’intrusion : vérifie la sécurité du logiciel en recherchant des vulnérabilités et en simulant des attaques potentielles
- …
Les tests unitaires
Nous allons utiliser le package pytest
pour réaliser nos tests en Python.
Un bon test unitaire
- Teste une seule fonctionnalité
- Isolé
- Reproductible
- Déterministe
- Isolé : indépendant des autres tests
- si vous testez une méthode A, qui elle-même appelle d’autres méthodes B, C, D…
- le test ne doit se faire que sur la méthode A
- il faut
mocker
le comportement des autres méthodes
- Déterministe : donne toujours le même résultat
Méthode à tester
operations_mathematiques.py
class OperationsMathematiques:
"""Opérations Mathématiques"""
def diviser_cinq_par(self, nombre) -> float:
"""Divise le nombre 5 par un nombre donné.
Parameters
----------
nombre : float or int
Le nombre par lequel 5 sera divisé.
Returns
-------
float or None
Le résultat de la division de 5 par le nombre donné.
Si le nombre est égal à 0, la méthode retourne None.
"""
if nombre != 0:
return 5 / nombre
else:
return None
Classe de test
Créons une classe de test.
Pour tester le cas nominal, nous :
- choisissons un nombre en entrée
- appelons la méthode
diviser_cinq_par()
- vérifions que la valeur retournée est égale à la valeur attendue
Cas Nominal
test_operations_mathematiques.py
import pytest
from mathematiques.operations_mathematiques import OperationsMathematiques
class TestOperationsMathematiques():
def test_diviser_cinq_par_nombre_non_nul(self):
# GIVEN
= 2
nombre
# WHEN
= OperationsMathematiques().diviser_cinq_par(nombre)
resultat
# THEN
assert resultat == 2.5
Est-ce suffisant ?
Autres cas
Mais ce n’est pas suffisant !
- La méthode a également un autre retour possible :
None
- Il faut aussi tester ce cas
test_operations_mathematiques.py
def test_diviser_cinq_par_zero(self):
# GIVEN
= 0
nombre
# WHEN
= OperationsMathematiques().diviser_cinq_par(nombre)
resultat
# THEN
assert resultat is None
Et si…
Nous appelons la méthode avec ce paramètre : diviser_cinq_par("a")
?
Vous pouvez aussi écrire un test pour vérifier que votre méthode renvoie bien une exception TypeError
dans ce cas.
test_operations_mathematiques.py
def test_diviser_cinq_string(self):
# GIVEN
= "a"
nombre
# WHEN / THEN
with pytest.raises(TypeError):
OperationsMathematiques().diviser_cinq_par(nombre)
Mais il est quand même préférable de vérifier dans votre méthode que le paramètre est bien de type numérique et de décider quoi faire si ce n’est pas le cas.
Ce qu’il faut retenir
Les tests unitaires
- vérifient qu’une méthode fait ce qu’elle doit faire
- il faut tester les cas nominaux, mais également les cas à la marge et les erreurs
- un test unitaire teste UNE et UNE seule chose
- il faut autant de tests unitaires que de retours possibles
Vu en 1A :
@pytest.mark.parametrize(
'a, b, resultat_attendu',
2, 3, 5),
[(2, 5, 7),
(3, 4, 7)]
( )
Mock
- Objet simulé qui remplace un composant réel lors des tests
- Permet de tester une unité de code en isolant ses dépendances externes
- Simuler des scénarios complexes comme les erreurs de réseau
Mock - exemple
joueur_service.py
class JoueurService:
def creer(self, pseudo, mdp, age, mail, fan_pokemon) -> Joueur:
= Joueur(
nouveau_joueur =pseudo,
pseudo=hash_password(mdp, pseudo),
mdp=age,
age=mail,
mail=fan_pokemon,
fan_pokemon
)= JoueurDao().creer(nouveau_joueur)
creation_ok if creation_ok:
return nouveau_joueur
else
return None
Comment prévoir le comportement de JoueurDao().creer(nouveau_joueur)
?
Mock - exemple
test_joueur_service.py
from unittest.mock import MagicMock
def test_creer_ok():
""" "Création de Joueur réussie"""
# GIVEN
= "jp", "1234", 15, "z@mail.oo", True
pseudo, mdp, age, mail, fan_pokemon = MagicMock(return_value=True)
JoueurDao().creer
# WHEN
= JoueurService().creer(pseudo, mdp, age, mail, fan_pokemon)
joueur
# THEN
assert joueur.pseudo == pseudo
Les Test Driven Development (TDD)
Quand tester ?
Au début !
Plus on teste tôt ➡️ plus les tests sont efficaces et peu coûteux !
Test Driven Development
La meilleure pratique :
- Créer les tests
- Coder la fonction
Logique
Cela paraît un peu étrange, mais en fait pas tant que ça.
Lorsque vous codez une fonction, vous savez avant de commencer :
- quels seront les paramètres en entrée
- quels résultats vous attendez en sortie
- donc vous savez déjà quoi tester !
Pratique du TDD
- ✅ Amélioration de la qualité du code
- ✅ Réduction des bugs
- ❌ puis ✅ Temps
- ❌ Maintenance des tests
Avantages >>> Incovénients
La pratique du TDD a le gros avantage que cela nous force à écrire des tests et de prendre le temps de bien faire les choses. Pour adhérer au TDD il faut vraiment se faire violence au début, mais au final cette pratique est très bénéfique.
Sinon, si l’on écrit la fonction en premier, une fois que l’on a terminé, il y a 9 chances sur 10 que l’on se dise : “c’est bon ça marche, pas la peine de tester…”. Et ça c’est pas bien !!!