c’est juste un fait de la vie, à mesure que le code grandit, vous devrez commencer à ajouter des moqueurs à votre suite de tests. Ce qui a commencé comme un joli petit projet de deux classes parle maintenant à des services externes et vous ne pouvez plus le tester confortablement.
C’est pourquoi Python est livré avecunittest.mock
, une partie puissante de la bibliothèque standard pour les dépendances de stubbing et les effets secondaires moqueurs.
Cependant, unittest.mock
n’est pas particulièrement intuitif.,
je me suis souvent demandé Pourquoi ma recette de référence ne fonctionnait pas pour un cas particulier, alors j’ai mis en place cette feuille de triche pour m’aider et aider les autres à se moquer rapidement.
Vous pouvez trouver les exemples de code dans le dépôt Github de l’article. J’utiliserai Python 3.6, si vous utilisez 3.2 ou moins, vous devrez utiliser le package mock
PyPI.,
Les exemples sont écrits en utilisant les classes unittest.TestCase
pour simplifier leur exécution sans dépendances, mais vous pouvez les écrire en tant que fonctions en utilisant pytest
presque directement,unittest.mock
fonctionnera très bien. Si vous êtes un utilisateurpytest
, je vous encourage à jeter un œil à l’excellente bibliothèquepytest-mock
.
le point central du module unittest.mock
est, bien sûr, la classe Mock
., La caractéristique principale d’un Mock
objet est qu’il sera de retour un autre Mock
exemple lorsque:
- accès à l’un de ses attributs
- l’appel de l’objet lui-même
C’est le comportement par défaut, mais il peut être remplacé dans différentes manières. Par exemple, vous pouvez attribuer une valeur à un attribut dans le Mock
par:
- attribuez-le directement, comme vous le feriez avec n’importe quel objet Python.,
- utilisez la méthode
configure_mock
sur une instance. - ou passez des arguments de mots clés à la classe
Mock
lors de la création.
Pour remplacer les appels à la maquette que vous aurez besoin de configurer son return_value
propriété, également disponible comme un argument mot-clé dans la balise Mock
initialiseur., Le Mock
retournera toujours la même valeur sur tous les appels, ceci, encore une fois, peut également être configuré en utilisant l’attribut side_effect
:
- Si vous souhaitez renvoyer des valeurs différentes à chaque appel, vous pouvez affecter un itérable à
side_effect
. - Si vous souhaitez déclencher une exception lors de l’appel de la maquette, vous pouvez simplement affecter l’objet exception à
side_effect
.,
Avec tous ces outils, nous pouvons maintenant créer des talons pour n’importe quel objet Python, qui sera parfait pour les entrées de notre système. Mais quid des résultats?
Si vous créez un programme CLI pour télécharger tout L’Internet, vous ne voulez probablement pas télécharger tout L’Internet à chaque test. Au lieu de cela, il suffirait d’affirmer que requests.download_internet
(pas une méthode réelle) a été appelée de manière appropriée. Mock
vous donne des méthodes pratiques pour le faire.,
Remarque: dans notre exemple: assert_called_once
a échoué, cela montre un autre aspect clé de la Mock
objets, ils enregistrent toutes les interactions avec eux et vous pouvez ensuite vérifier ces interactions.
Par exemple, vous pouvez utiliser call_count
pour récupérer le nombre d’appels à la balise Mock
, et utiliser call_args
ou call_args_list
pour inspecter les arguments de la dernière ou de tous les appels respectivement.,
Si cela n’est pas pratique à tout moment, vous pouvez utiliser la méthodereset_mock
pour effacer les interactions enregistrées, notez que la configuration ne sera pas réinitialisée, juste les interactions.
Enfin, permettez-moi de vous présenter MagicMock
, une sous-classe de: Mock
qui implémente par défaut de la magie ou dsous méthodes. Cela rend MagicMock
idéal pour simuler le comportement de classe, c’est pourquoi c’est la classe par défaut lors de la correction.,
Nous sommes maintenant prêts à commencer à nous moquer et à isoler l’unité en cours de test. Voici quelques recettes à garder à l’esprit:
Patch on import
la principale façon d’utiliser unittest.mock
est de patcher les importations dans le module testé en utilisant la fonction patch
.
patch
intercepteraimport
les instructions identifiées par une chaîne (plus à ce sujet plus tard), et retournera une instanceMock
que vous pouvez préconfigurer en utilisant les techniques que nous avons discutées ci-dessus.,
Imaginons que nous voulons tester cette fonction très simple:
Notez que nous sommes importateur os
et l’appel de getcwd
pour obtenir le répertoire de travail courant. Nous ne voulons pas vraiment l’appeler sur nos tests, car ce n’est pas important pour notre code et la valeur de retour peut différer entre les environnements sur lesquels il s’exécute.
comme mentionné ci-dessus, nous devons fournirpatch
avec une chaîne représentant notre importation spécifique., Nous ne voulons pas fournir simplement os.getcwd
car cela le patcherait pour tous les modules, nous voulons plutôt fournir le module testé import
de os
, c’est-à-dire work.os
. Lorsque le module est importé patch
fonctionne sa magie et renvoie un Mock
à la place.,
Sinon, on peut utiliser le décorateur version de patch
, remarque cette fois que le test a un paramètre supplémentaire: mocked_os
pour qui la balise Mock
est injecté dans le test.,
patch
va de l’avant les arguments mots-clefs de la balise Mock
de classe, afin de configurer un return_value
nous avons simplement l’ajouter en tant que:
les Moqueries des classes
C’est assez courant de patch classes complètement ou partiellement., Helper
par "db"
Worker
renvoie le chemin prévu fourni par Helper
afin de tester Worker
dans un isolement complet, nous avons besoin de patcher l’ensemble de l’ Helper
catégorie:
Notez le double return_value
dans l’exemple, simplement à l’aide de MockHelper.get_path.return_value
ne fonctionnait plus depuis dans le code que nous appelons get_path
sur une instance de la classe elle-même.,
la syntaxe de chaînage est légèrement déroutante mais n’oubliez pas queMagicMock
renvoie un autreMagicMock
lors des appels__init__
. Ici, nous configurons toutes les fausses instancesHelper
créées parMockHelper
pour renvoyer ce que nous attendons des appels àget_path
qui est la seule méthode qui nous intéresse dans notre test.,
spécification de classe
une conséquence de la flexibilité deMock
est qu’une fois que nous nous sommes moqués d’une classe, Python ne lèvera pasAttributeError
car il renverra simplement de nouvelles instances deMagicMock
pour fondamentalement tout. C’est généralement une bonne chose, mais cela peut entraîner un comportement déroutant et potentiellement des bugs. Par exemple, l’écriture de l’essai suivant,
en silence à passer avec aucun avertissement totalement absent de la faute de frappe dans la assrt_called_once
.,
en Outre, si nous étions à renommer Helper.get_path
de Helper.get_folder
, mais oublier de mettre à jour l’appel dans Worker
nos tests va toujours passer:
Heureusement Mock
est livré avec un outil pour éviter ces erreurs, speccing.
en termes simples, il préconfigure les mocks pour ne répondre qu’aux méthodes qui existent réellement dans la classe spec., Il existe plusieurs façons de définir les spécifications, mais le plus simple est de simplement passer autospec=True
à l’appel patch
, qui configurera le Mock
pour se comporter comme l’objet moqué, soulevant des exceptions pour les attributs manquants et les signatures incorrectes selon les besoins., Par exemple:
classe Partielle moqueur
Si vous êtes moins enclin à tester dans un isolement complet, vous pouvez également partiellement patch une classe à l’aide de patch.object
:
Ici patch.object
permet de configurer un moqué version de get_path
seulement, en laissant le reste du comportement intacte., Bien sûr, cela signifie que le test n’est plus ce que vous considéreriez strictement comme un test unitaire, mais vous pouvez être d’accord avec cela.
se moquant des fonctions intégrées et des variables d’environnement
dans les exemples précédents, nous avons négligé de tester un aspect particulier de notre classe simple, l’appelprint
. Si dans le contexte de votre application, cela est important, comme une commande CLI par exemple, nous devons faire des assertions contre cet appel.,
print
est, bien sûr, une fonction intégrée en Python, ce qui signifie que nous n’avons pas besoin de l’importer dans notre module, ce qui va à l’encontre de ce que nous avons discuté ci-dessus à propos du correctif à l’importation., Imaginez quand même que nous avions cette version un peu plus compliquée de notre fonction:
Nous Pouvons Écrire un test comme suit: