Exercice
Dans cet exercice, vous utilisez Pytest pour tester une fonction. Ensuite, vous trouvez et corrigez quelques problèmes potentiels avec la fonction qui entraîne l’échec de tests. Il est essentiel d’examiner les échecs et d’utiliser les rapports d’erreurs complets de Pytest pour identifier et corriger les bogues ou tests problématiques dans le code de production.
Pour cet exercice, nous utilisons une fonction appelée admin_command()
qui accepte une commande système comme entrée, et éventuellement la préfixer avec l’outil sudo
. La fonction présente un bogue que vous découvrez en écrivant des tests.
Étape 1 - Ajouter un fichier avec des tests pour cet exercice
Créez un fichier de test à l’aide des conventions de nom de fichier de Python pour les fichiers de test. Nommez le fichier de test test_exercise.py et ajoutez le code suivant :
def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: ["sudo"] + command return command
La fonction
admin_command()
prend une liste comme entrée en utilisant l’argumentcommand
et peut éventuellement préfixer la liste avecsudo
. Si l’argument de mot clésudo
est défini surFalse
, il retourne la même commande donnée comme entrée.Dans le même fichier, ajoutez les tests pour la fonction
admin_command()
. Les tests utilisent une méthode d’assistance qui retourne un exemple de commande :class TestAdminCommand: def command(self): return ["ps", "aux"] def test_no_sudo(self): result = admin_command(self.command(), sudo=False) assert result == self.command() def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() assert result == expected
Notes
Il n’est pas courant d’avoir des tests dans le même fichier que le vrai code. Pour faire simple, les exemples de cet exercice ont du vrai code dans le même fichier. Dans les vrais projets Python, les tests sont généralement séparés par des fichiers et des répertoires du code qu’ils testent.
Étape 2 : exécuter les tests et identifier la défaillance
Maintenant que le fichier de test a une fonction à tester et quelques tests pour vérifier son comportement, il est temps d’exécuter les tests et de traiter les échecs.
Exécutez le fichier avec Python :
$ pytest test_exercise.py
L’exécution doit se terminer avec un test réussi et un échec. La sortie d’échec doit ressembler à la sortie suivante :
=================================== FAILURES =================================== __________________________ TestAdminCommand.test_sudo __________________________ self = <test_exercise.TestAdminCommand object at 0x10634c2e0> def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() > assert result == expected E AssertionError: assert ['ps', 'aux'] == ['sudo', 'ps', 'aux'] E At index 0 diff: 'ps' != 'sudo' E Right contains one more item: 'aux' E Use -v to get the full diff test_exercise.py:24: AssertionError =========================== short test summary info ============================ FAILED test_exercise.py::TestAdminCommand::test_sudo - AssertionError: assert... ========================= 1 failed, 1 passed in 0.04s ==========================
La sortie échoue dans le test
test_sudo()
. Pytest fournit des détails sur les deux listes en cours de comparaison. Dans le cas présent, la variableresult
n’a pas la commandesudo
dedans, ce que le test attend.
Étape 3 : corriger le bogue pour que les tests réussissent
Avant d’apporter des modifications, vous devez comprendre pourquoi il y a un échec en premier lieu. Même si vous pouvez voir que le but souhaité n’a pas été atteint (sudo
n’est pas dans le résultat), vous devez trouver pourquoi.
Examinez les lignes de code suivantes de la fonction admin_command()
lorsque la condition sudo=True
est remplie :
if sudo:
["sudo"] + command
Actuellement, l’opération des listes n’est pas utilisée pour retourner la valeur. Étant donné qu’elle n’est pas retournée, la fonction finit par retourner la commande toujours sans sudo
.
Mettez à jour la fonction
admin_command()
pour retourner l’opération de liste afin que le résultat modifié soit utilisé lors de la demande d’une commandesudo
. La fonction mise à jour doit ressembler à ceci :def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: return ["sudo"] + command return command
Réexécutez le test avec Pytest. Essayez d’augmenter le niveau de détail de la sortie en utilisant l’indicateur
-v
avec Pytest :$ pytest -v test_exercise.py
Vérifiez ensuite la sortie. Elle doit maintenant afficher deux tests réussis :
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 cachedir: .pytest_cache rootdir: /private collected 2 items test_exercise.py::TestAdminCommand::test_no_sudo PASSED [ 50%] test_exercise.py::TestAdminCommand::test_sudo PASSED [100%] ============================== 2 passed in 0.00s ===============================
Notes
Étant donné que la fonction peut fonctionner avec davantage de valeurs avec une casse différente, des tests supplémentaires doivent être ajoutés pour couvrir ces variations. Cette opération vise à éviter que des modifications apportées ultérieurement à la fonction provoquent un comportement différent (inattendu).
Étape 4 : ajouter un nouveau code avec des tests
Après avoir ajouté des tests dans les étapes précédentes, vous devriez vous sentir à l'aise pour apporter d'autres modifications à la fonction et les vérifier à l'aide de tests. Même si les modifications ne sont pas couvertes par les tests existants, vous pouvez rester confiant, vous ne cassez aucune hypothèse précédente.
Dans le cas présent, la fonction admin_command()
approuve les yeux fermés que l’argument command
est toujours une liste. Améliorons cela en veillant à ce qu’une exception avec un message d’erreur utile soit déclenchée.
Tout d’abord, créez un test qui capture le comportement. Bien que la fonction n’a pas encore été mise à jour, essayez une approche « test-first » (également appelée développement piloté par les tests ou TDD).
- Mettez à jour le fichier test_exercise.py afin qu’il importe
pytest
en haut. Ce test utilise une assistance interne du frameworkpytest
:
import pytest
- Ajoutez maintenant un nouveau test à la classe pour vérifier l’exception. Ce test doit s’attendre à une
TypeError
de la fonction lorsque la valeur qui lui est transmise n’est pas une liste :
def test_non_list_commands(self): with pytest.raises(TypeError): admin_command("some command", sudo=True)
- Mettez à jour le fichier test_exercise.py afin qu’il importe
Réexécutez les tests avec Pytest, ils devraient tous aboutir :
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /private/ collected 3 items test_exercise.py ... [100%] ============================== 3 passed in 0.00s ===============================
Le test est suffisant pour vérifier
TypeError
, mais il serait judicieux d’ajouter le code avec un message d’erreur utile.Mettez à jour la fonction pour déclencher explicitement une
TypeError
avec un message d’erreur utile :def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if not isinstance(command, list): raise TypeError(f"was expecting command to be a list, but got a {type(command)}") if sudo: return ["sudo"] + command return command
Enfin, mettez à jour la méthode
test_non_list_commands()
pour rechercher le message d’erreur :def test_non_list_commands(self): with pytest.raises(TypeError) as error: admin_command("some command", sudo=True) assert error.value.args[0] == "was expecting command to be a list, but got a <class 'str'>"
Le test mis à jour utilise
error
comme variable qui contient toutes les informations d’exception. En utilisanterror.value.args
, vous pouvez examiner les arguments de l’exception. Dans le cas présent, le premier argument a la chaîne d’erreur que le test peut vérifier.
Vérifier votre travail
À ce stade, vous devez avoir un fichier de test Python appelé test_exercise.py qui inclut :
- Fonction
admin_command()
qui accepte un argument et un argument de mot clé. - Exception
TypeError
avec un message d’erreur utile dans la fonctionadmin_command()
. - Classe de test
TestAdminCommand()
qui a une méthode d’assistancecommand()
et trois méthodes de test qui vérifient la fonctionadmin_command()
.
Tous les tests doivent passer sans erreur lorsque vous les exécutez dans le terminal.