une Introduction à la moquerie en Python

comment exécuter des Tests unitaires en Python sans tester votre Patience

Le plus souvent, le logiciel que nous écrivons interagit directement avec ce que nous qualifierions de services « sales”. En termes simples: des services qui sont cruciaux pour notre application, mais dont les interactions ont des effets secondaires intentionnels mais indésirables-c’est-à-dire indésirables dans le cadre d’un test autonome.,

Par exemple: peut-être que nous sommes en train d’écrire une application sociale et que vous souhaitez tester notre nouveau ‘Post Facebook en vedette », mais vous ne voulez pas fait de post de Facebook à chaque fois que nous courons à notre suite de test.

la bibliothèque Pythonunittest comprend un sous—paquet nomméunittest.mock—ou si vous le déclarez comme une dépendance, simplementmock-qui fournit des moyens extrêmement puissants et utiles pour se moquer et éliminer ces effets secondaires indésirables.,

Note: mock est nouvellement inclus dans la bibliothèque standard de Python 3.3; les distributions a priori aurez à utiliser la Maquette de la bibliothèque téléchargeable via PyPI.

appels système vs. Python Mocking

pour vous donner un autre exemple, et celui que nous allons exécuter pour le reste de l’article, considérez les appels système., Il n’est pas difficile de voir que ce sont des candidats de choix pour se moquer: que vous écriviez un script pour éjecter un lecteur de CD, un serveur web qui supprime les fichiers de cache obsolètes De /tmp, ou un serveur socket qui se lie à un port TCP, ces appels présentent tous des effets

en tant que développeur, vous vous souciez davantage que votre bibliothèque ait appelé avec succès la fonction système pour éjecter un CD plutôt que de voir votre plateau de CD ouvert chaque fois qu’un test est exécuté.,

en tant que développeur, vous vous souciez plus que votre bibliothèque a appelé avec succès la fonction système pour éjecter un CD (avec les arguments corrects, etc.) par opposition à l’expérience réelle de votre plateau de CD ouvert à chaque fois qu’un test est exécuté. (Ou pire, plusieurs fois, car plusieurs tests référencent le code d’éjection lors d’une seule exécution de test unitaire!)

de même, garder vos tests unitaires efficaces et performants signifie garder autant de « code lent” hors des essais automatisés, à savoir l’accès au système de fichiers et au réseau.,

pour notre premier exemple, nous allons refactoriser un cas de test Python standard de la forme originale en un en utilisant mock. Nous allons démontrer comment l’écriture d’un cas de test avec mocks rendra nos tests plus intelligents, plus rapides et capables d’en révéler plus sur le fonctionnement du logiciel.

une simple fonction de suppression

Nous avons tous besoin de supprimer des fichiers de notre système de fichiers de temps en temps, alors écrivons une fonction en Python qui facilitera un peu la tâche de nos scripts.,

#!/usr/bin/env python# -*- coding: utf-8 -*-import osdef rm(filename): os.remove(filename)

évidemment, notre méthoderm à ce stade, ne fournit pas beaucoup plus que la méthodeos.remove sous-jacente, mais notre base de code s’améliorera, nous permettant d’ajouter plus de fonctionnalités ici.

écrivons un cas de test traditionnel, c’est-à-dire sans moqueries:

notre cas de test est assez simple, mais chaque fois qu’il est exécuté, un fichier temporaire est créé puis supprimé., De plus, nous n’avons aucun moyen de tester si notre méthode rm transmet correctement l’argument à l’appel os.remove. Nous pouvons supposer que c’est le cas sur la base du test ci-dessus, mais beaucoup reste à désirer.

Refactoring avec Python Mocks

refactorisons notre cas de test en utilisantmock:

avec ces refactors, nous avons fondamentalement changé la façon dont le test fonctionne. Maintenant, nous avons un initié, un objet que nous pouvons utiliser pour vérifier la fonctionnalité de l’autre.,

pièges potentiels de moquerie Python

l’une des premières choses qui devraient ressortir est que nous utilisons le décorateur de méthodemock.patch pour simuler un objet situé àmymodule.os, et injectons cette simulation dans notre méthode de cas de test. N’aurait – il pas plus de sens de simplement se moquer de os lui-même, plutôt que de la référence à mymodule.os?

Eh bien, Python est un peu un serpent sournois en ce qui concerne les importations et la gestion des modules., Au moment de l’exécution, le module mymodule a son propre os qui est importé dans sa propre portée locale dans le module. Ainsi, si nous nous moquons de os, nous ne verrons pas les effets de la simulation dans le module mymodule.

Le mantra à répéter est ceci:

se Moquer un article où il est utilisé, non pas d’où il vient.,

Si vous avez besoin de se moquer de l’ tempfile module myproject.app.MyElaborateClass, vous avez probablement besoin d’appliquer la maquette de myproject.app.tempfile, comme chaque module conserve ses propres importations.

avec cet écueil à l’écart, continuons à nous moquer.

ajouter une Validation à ‘rm’

la méthoderm définie précédemment est assez simplifiée. Nous aimerions qu’il valide qu’un chemin existe et qu’il s’agit d’un fichier avant de tenter aveuglément de le supprimer., Nous allons refactoriser le code rm pour être un peu plus intelligent:

#!/usr/bin/env python# -*- coding: utf-8 -*-import osimport os.pathdef rm(filename): if os.path.isfile(filename): os.remove(filename)

la Grande. Maintenant, ajustons notre cas de test pour maintenir la couverture.

notre paradigme de test a complètement changé. Nous pouvons maintenant vérifier et valider la fonctionnalité interne des méthodes sans aucun effet secondaire.

File-Removal as a Service with Mock Patch

Jusqu’à présent, nous n’avons travaillé qu’avec la fourniture de mocks pour les fonctions, mais pas pour les méthodes sur les objets ou les cas où le mocking est nécessaire pour envoyer des paramètres. Couvrons d’abord les méthodes d’objet.,

nous allons commencer par un refactor de la méthoderm dans une classe de service. Il n’y a vraiment pas de besoin justifiable, en soi, d’encapsuler une fonction aussi simple dans un objet, mais cela nous aidera à tout le moins à démontrer les concepts clés dans mock. Refactorisons:

vous remarquerez que peu de choses ont changé dans notre cas de test:

génial, donc nous savons maintenant que leRemovalService fonctionne comme prévu., Créons un autre service qui le déclare comme une dépendance:

puisque nous avons déjà une couverture de test sur laRemovalService, nous n’allons pas valider la fonctionnalité interne de larm méthode dans nos tests deUploadService. Au contraire, nous allons simplement tester (sans effets secondaires, bien sûr) que UploadService appelle la RemovalService.rm méthode, que nous savons « just works™” de notre cas de test précédent.

Il y a deux façons d’aller à ce sujet:

  1. se Moquer de la RemovalService.rm méthode elle-même.,
  2. fournit une instance moquée dans le constructeur deUploadService.

comme les deux méthodes sont souvent importantes dans les tests unitaires, nous examinerons les deux.

Option 1: moquer les méthodes D’Instance

la bibliothèque mocka un décorateur de méthode spécial pour moquer les méthodes et propriétés d’instance d’objet, le@mock.patch.object décorateur:

génial! Nous avons validé que la UploadService appelle avec succès la méthode rm de notre instance. Remarquez quelque chose d’intéressant là-dedans?, Le mécanisme de correction a en fait remplacé la méthoderm de toutes les instancesRemovalService dans notre méthode de test. Cela signifie que nous pouvons réellement inspecter les instances elles-mêmes. Si vous voulez en voir plus, essayez de supprimer un point d’arrêt dans votre code moqueur pour avoir une bonne idée du fonctionnement du mécanisme de patch.

écueil du Patch simulé: Ordre des décorateurs

lorsque vous utilisez plusieurs décorateurs sur vos méthodes de test, l’ordre est important et c’est un peu déroutant. Fondamentalement, lorsque vous mappez des décorateurs sur des paramètres de méthode, travaillez à l’envers., Considérez cet exemple:

remarquez comment nos paramètres sont appariés à l’ordre inverse des décorateurs? C’est en partie à cause de la façon dont Python fonctionne. Avec plusieurs méthode décorateurs, voici l’ordre d’exécution en pseudo-code:

patch_sys(patch_os(patch_os_path(test_something)))

Depuis le patch sys est ultrapériphériques patch, il sera exécuté dernier, faisant d’elle le dernier paramètre dans la méthode d’essai arguments. Prenez bien en note et utilisez un débogueur lors de l’exécution de vos tests pour vous assurer que les bons paramètres sont injectés dans le bon ordre.,

Option 2: Création d’Instances simulées

Au Lieu de se moquer de la méthode d’instance spécifique, nous pourrions simplement fournir une instance simulée àUploadService avec son constructeur. Je préfère l’option 1 ci-dessus, car elle est beaucoup plus précise, mais il existe de nombreux cas où l’option 2 peut être efficace ou nécessaire. Refactorisons à nouveau notre test:

dans cet exemple, nous n’avons même pas eu à patcher de fonctionnalité, nous créons simplement une spécification automatique pour la classe RemovalService, puis injectons cette instance dans notre UploadService pour valider la fonctionnalité.,

la méthode mock.create_autospec crée une instance fonctionnellement équivalente à la classe fournie. Ce que cela signifie, en pratique, c’est que lorsque l’instance renvoyée interagit avec, elle déclenchera des exceptions si elle est utilisée de manière illégale. Plus précisément, si une méthode est appelée avec le mauvais nombre d’arguments, une exception sera levée. Ceci est extrêmement important car les refactors se produisent. Comme une bibliothèque change, les tests se brisent et cela est attendu. Sans utiliser une spécification automatique, nos tests passeront toujours même si l’implémentation sous-jacente est cassée.,

Écueil: La balise mock.Mock et mock.MagicMock Classes

Le mock bibliothèque comprend également deux classes pour lesquelles la plupart de fonctionnement repose sur: mock.Mock et mock.MagicMock. Lorsque vous avez le choix d’utiliser une instance mock.Mock, une instance mock.MagicMock ou une spécification automatique, privilégiez toujours l’utilisation d’une spécification automatique, car cela aide à garder vos tests sains pour les changements futurs., En effet, mock.Mock Et mock.MagicMock acceptent tous les appels de méthode et les affectations de propriétés quelle que soit L’API sous-jacente. Considérez le cas d’utilisation suivant:

class Target(object): def apply(value): return valuedef method(target, value): return target.apply(value)

Nous pouvons tester cela avec une instance mock.Mock comme ceci:

cette logique semble saine, mais modifions la méthode Target.apply pour prendre plus de paramètres:

class Target(object): def apply(value, are_you_sure): if are_you_sure: return value else: return None

réexécutez votre test, et vous constaterez qu’il passe toujours. C’est parce qu’il n’est pas construit sur votre API réelle., C’est pourquoi vous devriez toujours utiliser la balise create_autospec méthode et le autospec paramètre de la balise @patch et @patch.object décorateurs.

Python se Moquer Exemple: se moquant d’un Facebook Appel d’API

Pour finir, nous allons écrire une plus applicable dans le monde réel python se moquer exemple, qui nous l’avons mentionné dans l’introduction: l’affichage d’un message à Facebook. Nous allons écrire une belle classe wrapper et un cas de test.,

Voici notre cas de test, qui vérifie que nous publions le message sans le poster réellement:

Comme nous l’avons vu jusqu’à présent, il est vraiment simple de commencer à écrire des tests plus intelligents avecmock en Python.

Conclusion

la bibliothèquemock de Python, si elle est un peu déroutante à utiliser, change la donne pour les tests unitaires. Nous avons démontré des cas d’utilisation courants pour commencer à utiliser mock dans les tests unitaires, et nous espérons que cet article aidera les développeurs Python à surmonter les obstacles initiaux et à écrire un excellent code testé.,

Share

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *