L'objectif de ce repository est de démontrer l'importance des tests dans le développement d'un logiciel. Il a été créé dans le cadre du cours de Veille Technologique dans ma troisième année de BUT Informatique à l'université de La Rochelle adin de servir de démonstration pendant notre présentation. Ce repository contient un mini-projet de calculatrice réalisé en Python, capable d'effectuer des opérations de base (addition, soustraction, multiplication, division), et de les enregistrer dans une base de donnée locale. Le projet contient plusieurs branches, chacune représentant une couche de tests supplémentaire.
Ce mini-projet est réalisé en Python 3.10.12 mais il est possible de l'exécuter avec une version antérieure de Python 3. Pour lancer le projet, il suffit de cloner le repository et d'exécuter le fichier main.py
avec la commande python3 main.py
. Pour une expérience améliorée, vous pouver ajouter la police de caractère Digital-7.ttf
à votre système. Vous devrez également avoir installer sqlite3
pour pouvoir utiliser la base de données et pytest
et pytest-cov
pour lancer les tests.
Rendez-vous sur la branche step-0-no-test
pour voir le code de la première version du projet. Cette version ne contient aucun test. Pourtant, elle fonctionne correctement, vous pouvez effectuer des opérations de base avec la calculatrice sans rencontrer de problèmes. On pourrait alors penser que les tests ne sont pas nécessaires.
Définition : Ces tests se concentrent sur les plus petites unités de vos logiciels, c'est-à-dire vos méthodes et fonctions. Ils s'assurent que la sortie de vos méthode et fonction est celle attendu pour une entrée donnée.
Rendez-vous sur la branche step-1-unit-test
pour voir le code de la deuxième version du projet. Cette version contient des tests unitaires pour les fonctions de la calculatrice. Ces tests permettent de vérifier que chaque fonction de la calculatrice fonctionne correctement.
Executez la commande python3 -m pytest --cov=calculator --cov-report=html --cov-report=term
pour lancer les tests.
On obtient le résultat suivant :
---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name Stmts Miss Cover
---------------------------------------------------
calculator/CalculatorApp.py 105 105 0%
calculator/CalculatorLogic.py 39 1 97%
calculator/DatabaseManager.py 28 0 100%
---------------------------------------------------
TOTAL 172 106 38%
Coverage HTML written to dir htmlcov
======short test summary info ======
FAILED tests/UnitTests/test_CalculatorLogic.py::test_division_by_zero - ZeroDivisionError: division by zero
FAILED tests/UnitTests/test_CalculatorLogic.py::test_calcul_division_by_zero - ZeroDivisionError: float division by zero
======2 failed, 22 passed in 0.39s =====
On voit que deux tests ont échoué. Ces tests ont permis de révéler des erreurs dans le code de la fonction divide
de la classe Calculator
. En effet, en regardant le code source, on voit que la division par zéro n'est pas prise en compte.
def divide(self, a, b):
"""Do the division."""
return a / b
On peut modifier la fonction divide
de la classe Calculator
pour prendre en compte la division par zéro :
def divide(self, a, b):
"""Do the division."""
if b == 0:
return "Syntax error"
return a / b
On peut maintenant relancer les tests pour vérifier que les erreurs ont été corrigées.
---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name Stmts Miss Cover
---------------------------------------------------
calculator/CalculatorApp.py 105 105 0%
calculator/CalculatorLogic.py 41 1 98%
calculator/DatabaseManager.py 28 0 100%
---------------------------------------------------
TOTAL 174 106 39%
Coverage HTML written to dir htmlcov
==== 24 passed in 0.24s ====
On voit que les tests passent tous, ils ont permis de révéler des erreurs dans le code et de les corriger. On voit également que la couverture des tests est de n'est que de 39%. Il reste donc des parties du code qui ne sont pas testées. Il faudra ajouter des tests pour ces parties du code.
Définition : Ces tests se concentrent sur les interactions entre les différentes unités de votre logiciel. Ils s'assurent que les différentes unités de votre logiciel fonctionnent correctement ensemble.
Rendez-vous sur la branche step-2-integration-test
pour voir le code de la troisième version du projet. Cette version contient des tests d'intégration portant sur les interactions entre les différentes classes de la calculatrice. Ces tests permettent de vérifier que les différentes classes de la calculatrice fonctionnent correctement ensemble.
Executez la commande python3 -m pytest --cov=calculator --cov-report=html --cov-report=term
pour lancer les tests.
On obtient le résultat suivant :
---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name Stmts Miss Cover
---------------------------------------------------
calculator/CalculatorApp.py 105 7 93%
calculator/CalculatorLogic.py 41 1 98%
calculator/DatabaseManager.py 28 0 100%
---------------------------------------------------
TOTAL 174 8 95%
Coverage HTML written to dir htmlcov
====short test summary info ====
FAILED tests/IntegrationTests/test_integration.py::test_calculation_integration - AssertionError: The result should be '9.2' in the database.
==== 1 failed, 27 passed in 0.91s ====
On voit que un test a échoué. Ce test a permis de révéler une erreur dans le code de la fonction get_result
de la classe CalculatorApp
. En effet, en regardant le code source, on voit que les résultats sont convertis en entier avant d'être enregistrés dans la base de données.
def get_result(self):
"""Calculate the result, display it and save it."""
first_value = self.first_value_entry.get()
operator = self.operator_entry.get()
second_value = self.second_value_entry.get()
self.calculator_logic.calcul[0] = first_value
self.calculator_logic.calcul[1] = operator
self.calculator_logic.calcul[2] = second_value
result = self.calculator_logic.calculate_result()
self.db_manager.save_calculation(first_value, operator, second_value, int(result)) #<----
self.show_history()
self.result_display.config(text=str(result))
On peut retirer la conversion en entier pour que les résultats soient enregistrés correctement dans la base de données :
def get_result(self):
"""Calculate the result, display it and save it."""
first_value = self.first_value_entry.get()
operator = self.operator_entry.get()
second_value = self.second_value_entry.get()
self.calculator_logic.calcul[0] = first_value
self.calculator_logic.calcul[1] = operator
self.calculator_logic.calcul[2] = second_value
result = self.calculator_logic.calculate_result()
self.db_manager.save_calculation(first_value, operator, second_value, result) #<----
self.show_history()
self.result_display.config(text=str(result))
On peut maintenant relancer les tests pour vérifier que les erreurs ont été corrigées.
---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name Stmts Miss Cover
---------------------------------------------------
calculator/CalculatorApp.py 105 7 93%
calculator/CalculatorLogic.py 41 1 98%
calculator/DatabaseManager.py 28 0 100%
---------------------------------------------------
TOTAL 174 8 95%
Coverage HTML written to dir htmlcov
===== 28 passed in 0.86s ======
On voit que les tests passent tous, ils ont permis de révéler des erreurs dans le code et de les corriger. On voit également que la couverture des tests est de 95%. C'est mieux mais il reste encore des parties du code qui ne sont pas testées. Il faudra ajouter des tests pour ces parties du code.
Définition : Ces tests se concentrent sur les fonctionnalités de votre logiciel. Ils s'assurent que votre logiciel fonctionne correctement pour l'utilisateur final. Ils simulent le déroulement d'un scénario métier (souvent définit par les exigences métier ou fonctionnels), sans prêter attention aux états intermédiaire de l'application mais en vérifiant les résultats finaux.
Rendez-vous sur la branche step-3-functional-test
pour voir le code de la quatrième version du projet git branch step-3-functional-test
. Cette version contient des tests fonctionnels portant sur les fonctionnalités de la calculatrice. Ces tests permettent de vérifier que la calculatrice fonctionne correctement pour l'utilisateur final.
Executez la commande python3 -m pytest --cov=calculator --cov-report=html --cov-report=term
pour lancer les tests.
On obtient le résultat suivant :
---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name Stmts Miss Cover
---------------------------------------------------
calculator/CalculatorApp.py 105 2 98%
calculator/CalculatorLogic.py 41 1 98%
calculator/DatabaseManager.py 28 0 100%
---------------------------------------------------
TOTAL 174 3 98%
Coverage HTML written to dir htmlcov
===== short test summary info =====
FAILED tests/FonctionalTests/test_functional.py::test_calculate_result - AssertionError: assert 3.0 == '3.0'
===== 1 failed, 34 passed in 3.63s ======
On voit que un test a échoué. Ce test a permis de révéler une erreur dans le code de la fonction get_result
de la classe CalculatorApp
. En effet, en regardant le code source, on voit que le contenu du label n'est pas converti en string avant d'être affiché.
def get_result(self):
"""Calculate the result, display it and save it."""
first_value = self.first_value_entry.get()
operator = self.operator_entry.get()
second_value = self.second_value_entry.get()
self.calculator_logic.calcul[0] = first_value
self.calculator_logic.calcul[1] = operator
self.calculator_logic.calcul[2] = second_value
result = self.calculator_logic.calculate_result()
self.db_manager.save_calculation(first_value, operator, second_value, result)
self.show_history()
self.result_display.config(text=result) #<----
On peut convertir le résultat en string avant de l'afficher :
def get_result(self):
"""Calculate the result, display it and save it."""
first_value = self.first_value_entry.get()
operator = self.operator_entry.get()
second_value = self.second_value_entry.get()
self.calculator_logic.calcul[0] = first_value
self.calculator_logic.calcul[1] = operator
self.calculator_logic.calcul[2] = second_value
result = self.calculator_logic.calculate_result()
self.db_manager.save_calculation(first_value, operator, second_value, result)
self.show_history()
self.result_display.config(text=str(result)) #<----
On peut maintenant relancer les tests pour vérifier que les erreurs ont été corrigées.
---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name Stmts Miss Cover
---------------------------------------------------
calculator/CalculatorApp.py 105 2 98%
calculator/CalculatorLogic.py 41 1 98%
calculator/DatabaseManager.py 28 0 100%
---------------------------------------------------
TOTAL 174 3 98%
Coverage HTML written to dir htmlcov
==== 35 passed in 3.55s ======
On voit que les tests passent tous, ils ont permis de révéler des erreurs dans le code et de les corriger. On voit également que la couverture des tests est de 98%. C'est un trés bon score, on peut considérer que cette couverture est suffisante pour ce projet, les prochains tests que nous allons ajouter seront plus orienté performance.
Définition : Ces tests se concentrent sur les performances de votre logiciel. Ils s'assurent que votre logiciel fonctionne correctement en terme de temps d'exécution, de consommation de ressources, etc.
Rendez-vous sur la branche step-4-performance-test
pour voir le code de la cinquième version du projet git branch step-4-performance-test
. Cette version contient des tests de performance portant sur les performances de la calculatrice. Ces tests permettent de vérifier que la calculatrice execute les opérations de base dans un temps cible. Notez que on fonction de votre ordinateur, les temps cibles peuvent être à adapter.
Executez la commande python3 -m pytest --cov=calculator --cov-report=html --cov-report=term
pour lancer les tests.
On obtient le résultat suivant :
---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name Stmts Miss Cover
---------------------------------------------------
calculator/CalculatorApp.py 105 2 98%
calculator/CalculatorLogic.py 41 1 98%
calculator/DatabaseManager.py 28 0 100%
---------------------------------------------------
TOTAL 174 3 98%
Coverage HTML written to dir htmlcov
===== short test summary info =====
FAILED tests/PerformanceTests/test_performance.py::test_multiplication_performance_time - AssertionError: Time taken too long: 2.998039484024048 seconds
===== 1 failed, 38 passed in 10.05s =====
On voit que un test a échoué. Ce test a permis de révéler une erreur dans le code de la fonction multiply
de la classe CalculatorLogic
. En effet, en regardant le code source, on voit que la fonction multiply
contient une boucle inutile qui ralentit l'exécution
def multiply(self, a, b):
"""Do the multiplication."""
for i in range(100000):
a * b + i
return a * b
On peut retirer la boucle inutile pour améliorer les performances de la fonction multiply
:
def multiply(self, a, b):
"""Do the multiplication."""
return a * b
On peut maintenant relancer les tests pour vérifier que les erreurs ont été corrigées.
---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name Stmts Miss Cover
---------------------------------------------------
calculator/CalculatorApp.py 105 2 98%
calculator/CalculatorLogic.py 39 1 97%
calculator/DatabaseManager.py 28 0 100%
---------------------------------------------------
TOTAL 172 3 98%
Coverage HTML written to dir htmlcov
==== 39 passed in 7.39s =====
On voit que les tests passent tous, ils ont permis de révéler des erreurs dans le code et de les corriger.
Définition : Ces tests consistent à explorer librement l'application sans suivre de script de test précis, en recherchant des anomalies ou des comportements inattendus.
Pour illustrer cette partie, je vous propose de regarder la vidéo suivante : Session de test exploratoire
Vous assistez a une session de test exploratoire organisé avec 4 testeur/testeuse. Ceux-ci ont pour mission de tester la calculatrice et de trouver des bugs. Ils ont 30 minutes pour réaliser cette mission. Il ont un bloc note pour noter les bugs qu'ils trouvent. Lorsqu'un bug est trouvé, il est signalé à l'organisateur de la session.
On a donc vu qu'un utilisateur a eu l'idée de rentrer des lettres avec son clavier dans les champs de la calculatrice. Ceci est gérer par l'application mais génère une erreur de syntaxe, ce qui n'est pas très explicite pour l'utilisateur. On pourrait améliorer cela en affichant un message d'erreur plus explicite par exemple.
On a vu que les tests sont très importants dans le développement d'un logiciel. Ils permettent de révéler des erreurs dans le code et de les corriger. Ils permettent également de s'assurer que le logiciel fonctionne correctement pour l'utilisateur final. Il est donc important de prendre le temps de rédiger des tests pour son code à plusieurs niveaux : unitaire, intégration, fonctionnel, performance, exploratoire. Chaque niveau de test permet de révéler des erreurs différentes dans le code.