- Introduction
- unittest.,mock or mock
- Decorator
- Resource location
- Mock return_value vs side_effect
- Appels Imbriqués Mock
- Verify Exceptions
- Clearing lru_cache
- Mock Module Level/Global Variables
- Méthode d’instance Mock
- Méthode de classe Mock
- Classe Entière Mock
- Appels Asynchrones Mock
- Types d’instance simulés
- Builtin simulé
open
fonction - Conclusion
Introduction
Se moquer des ressources lors de l’écriture de tests en Python peut être déroutant si vous n’êtes pas familier avec de telles choses., Dans cet article, je vais couvrir divers aspects du code moqueur, qui, espérons-le, sera une ressource utile pour ceux qui sont un peu coincés.
Remarque: dans les exemples de code, j’utilise pytest, mais pour la plupart, cela ne devrait pas avoir d’importance.
unittest.mock ou mock
Pour « moquer » une ressource, nous aurons d’abord besoin du module mock
, et c’est notre première pierre d’achoppement: de quelle version avons-nous besoin? c’est-à-dire qu’il y en a deux et qu’ils semblent tous deux officiels (mock
et unittest.mock
).,
Le mock
le module est compatible bibliothèque que vous pouvez télécharger à partir de PyPy, où unittest.mock
est la même chose, mais uniquement compatible avec la version de Python que vous utilisez.,
Donc, dans presque tous les cas, vous voudrez l’importer comme suit:
import unittest.mock as mock
Pour plus d’exemples, consultez ce guide de référence
Décorateur
La façon la plus courante de se moquer les ressources sont d’utiliser un décorateur Python autour de votre fonction de test:
@mock.patch("thing")def test_stuff(mock_thing): mock_thing.return_value = 123
Dans ce cas, ce que nous corrigeons (thing
) peut être une variable ou une fonction.,
Lorsque vous faites cela, vous devrez passer un argument à votre fonction (vous pouvez le nommer comme vous voulez †) qui sera unMagicMock
.
Cela signifie que si vous ne faites rien d’autre, alors les appels à thing
(dans l’exemple ci-dessus au moins) le résultat d’une valeur 123
retourné.
† convention est le nom de la variable
mock_<noun>
.,
Si vous vous moquez de plusieurs choses, vous empilerez les décorateurs simulés les uns sur les autres et les transmettrez à la fonction de test:
@mock.patch("third")@mock.patch("second")@mock.patch("first")def test_stuff(mock_first, mock_second, mock_third): ...
Resource location
Il est important de savoir que lorsque vous vous moquez, vous devez spécifier c’est importé., C’est mieux expliqué par exemple…
Imaginons que j’ai un module app.foo
et dans ce module je importer une autre dépendance de la sorte:
from app.bar import thing
Vous pourriez penser que lorsque vous appelez mock.patch
que vous avez passer une référence à la ressource comme app.bar.thing
. Cela ne serait pertinent que si la ressource était appelée avec ce chemin complet dans le module app.foo
(par exemple si app.foo
appelé app.bar.thing(...)
).,
Si le chemin de l’espace de noms complet n’est pas référencé, ce qui n’est pas le cas dans l’exemple ci-dessus (notez que nous importons uniquement la ressource thing
). Cela signifie que nous devons spécifier la référence de l’espace de noms de fantaisie que là où il est importé:
@mock.patch('app.foo.thing')
Donc, même si thing
existe à l’intérieur de la balise app.bar
nous spécifier app.foo.thing
app.foo
est l’endroit où nous avons importé pour l’utiliser. Cela attrape les gens tout le temps.,
Mock return_value vs side_effect
Si votre fonction a un try/except autour d’elle, vous pouvez utiliser side_effect
pour provoquer l’appel de la fonction pour déclencher une exception en tant que valeur renvoyée:
@mock.patch('app.aws.sdk.confirm_sign_up', side_effect=Exception('whoops'))
Remarque: si vous aviez utilisé
return_value=Exception('whoops')
alors la maquette retournerait la représentation de chaîne de l’exception plutôt que de déclencher une exception commeside_effect
le fait.,la raison pour laquelle cela peut devenir plus compliqué est due à la façon dont une maquette renverra une nouvelle maquette lors de l’accès à une propriété sur une maquette:Le code ci-dessus sera une erreur:
AssertionError: expected call not found.Expected: listen(8080)Actual: listen(123)
Vous devrez vous assurer que vous affirmez la maquette au bon moment:
Vérifier les exceptions
Si de code jette un type
Exception
lorsque nous en avons besoin, nous pouvons nous moquer de ressources spécifiques pour lancer une exception, puis utiliserpytest.raises
en tant que gestionnaire de contexte autour de l’appelant de notre code à vérifier.,Nous pouvons attraper et faire des assertions contre ce comportement attendu en nous moquant d’abord de la ressource que nous voulons lancer une exception et l’amener à lancer notre propre fausse exception en utilisant le paramètre
side_effect
.Ensuite, nous préciser la type d’exception nous nous attendons à être soulevée à l’aide de
pytest.raises(T)
:Remarque: ne faites pas l’erreur de mettre des assertions dans le
with
gestionnaire de contexte., Une fois que l’exception est levée par la fonction appelée dans le gestionnaire de contextewith
, tout le code après dans le bloc est ignoré.Effacer lru_cache
Si une fonction que vous souhaitez tester a le décorateur
functools.lru_cache
appliqué, alors vous devrez être conscient de se moquer de la réponse de cette fonction car elle sera mise en cache dans un test et le résultat mis en cache sera vous confondre quand vous voyez la réponse inattendue).,Pour résoudre ce problème est très facile, car les
lru_cache
offre d’autres fonctions lorsque decoratoring vos fonctions, il assure:
cache_info
cache_clear
Le dernier (
cache_clear
) est ce que vous auriez besoin de faire appel. Ceci est démontré ci-dessous:Remarque: le débogage n’est pas toujours évident., Plus tard, je montre comment se moquer de la fonction
open
intégrée, et dans ce scénario, je suis tombé sur ce problème, car bien que je ne me moquais pas de la fonction de niveau supérieur elle-même (je me moquais de l’appel àopen
à l’intérieur), le contenuse Moquer de Niveau Module/Variables Globales
Avec un module variable vous pouvez vous pouvez définir la valeur directement, ou utiliser
mock.patch
.,Dans l’exemple suivant, nous avons la variable
client_id
qui est une variable globale à l’intérieur du moduleapp.aws
que nous importons pour référencer ailleurs dans notre code:Dans l’exemple
mock.patch
, il y a deux>nous n’utilisons pas return_value
.il n’y a pas d’instance simulée passée à la fonction de test., C’est parce que nous modifions une variable et non une fonction directe ou « appelable », il n’est donc pas nécessaire de passer une simulation dans la fonction de test (si vous voulez changer la valeur plusieurs fois dans le test lui-même, vous vous moquerez de la variable mais n’affecterez pas immédiatement une valeur dans le décorateur).
Méthode d’instance simulée
Il existe plusieurs façons de se moquer d’une méthode d’instance., Une approche courante consiste à utiliser
mock.patch.object
, comme ceci:Une autre approche consiste à se moquer de la méthode comme vous le feriez avec une fonction normale, mais vous référencez la méthode via le nom de classe:
Une autre approche (bien que plus lourde) pour se moquer d’une méthode div>
Remarque: dans l’exemple ci-dessus, nous nous moquons de la classe entière, ce qui pourrait ne pas être ce que vous voulez. Sinon, utilisez plutôt l’exemple précédent
mock.patch.object
.,La raison pour laquelle l’exemple ci-dessus fonctionne est parce que nous sommes la configuration de
return_value
sur notre maquette. Parce qu’il s’agit d’unMagicMock
chaque attribut référencé renvoie une nouvelle instance de simulation (une fonction ou une propriété que vous appelez sur une simulation n’a pas besoin d’exister) et nous appelons doncmade_up_function
sur la simulation retournée, et sur cette simulation nouvellement créée, nous définissons 420ec07c94″>.,Mais comme mentionné dans la note ci-dessus, cette approche pourrait être un peu trop brutale en fonction de vos besoins (que vous vous souciiez d’avoir une classe de fonctionnement ou non).
Méthode de classe simulée
Pour se moquer d’une méthode de classe est une approche similaire à se moquer d’une méthode d’instance.,
Une approche pourrait être que vous vous moquez de la classe entière (mais maintenant vous en avez une de moins
return_value
à attribuer):mock_class.ClassMethodName.return_value = "123"
Ou mieux encore, vous devriez vous moquer comme vous le feriez avec n’importe quelle fonction normale, mais référencez simplement la méthode classe vous devrez définir le
return_value
pour être une nouvelle instance de la classe.,Voir d’autres conseils de moquerie liés à la classe ici
Appels asynchrones simulés
Se moquer du code asynchrone est probablement l’aspect le plus déroutant de la moquerie. Ma solution « aller à », je vais d’abord expliquer, mais après cela, je vais partager quelques méthodes alternatives que j’ai vues et essayées dans le passé.,
d’Abord considérer ce code asynchrone à l’intérieur d’une balise
app.foo
module:import app.stuffasync def do_thing(x): return await app.stuff.some_concurrent_function(x)
Si nous avons besoin de nous moquer de la coroutine
app.stuff.some_concurrent_function
, alors nous pouvons résoudre ce problème en créant une fonction qui agit comme une coroutine et lui permettre d’être configurable pour différents types de réponses:Remarque: cet exemple utilise tornade pour l’exécution asynchrone de test.,e alternatives…
AsyncMock
Remarque: cela utilise le package
pytest-asyncio
pour aider à tester le code asyncioCommençons par le code à se moquer…
import asyncioasync def sum(x, y): await asyncio.sleep(1) return x + y
Maintenant, voici comment nous nous moquerions it
Monkey Patch
MagicMock Subclass
Async Inline Function
Mock Instance Types
Lorsque vous vous moquez d’un objet, vous constatez que la maquette remplace l’objet entier et peut donc entraîner la réussite (ou l’échec) des tests de manière inattendue.,
ce qui Signifie, si vous avez besoin de faire une simulation de plus comme l’interface concrète, alors il ya deux façons de le faire:
spec
wrap
On peut utiliser mock
spec
fonctionnalité à imiter toutes les méthodes/attributs de l’objet que l’on se moque. Cela garantit que vos mocks ont la même API que les objets qu’ils remplacent.Remarque: il n’est plus strict
spec_set
qui génère une baliseAttributeError
.,Ceci est mieux démontré avec un exemple:
Le paramètre
wrap
d’autre part vous permet d ‘ « espionner » l’implémentation, ainsi que d’affecter son comportement.,sur ce qui précède qui se moque de l’objet entier, pas seulement d’une seule méthode:Mock builtin open function
La bibliothèque mock de Python fournit une abstraction pour se moquer de la fonction
open
beaucoup plus simpleLe paramètre
create=True
2d12c3cd62″> signifie que lemock.MagicMock
retourné créera automatiquement tous les attributs appelés sur la maquette (c’est parce que la fonctionopen
tentera d’accéder à beaucoup de choses différentes et il est plus facile pour mock de se moquer de tout cela pour vous).,Conclusion
Là, nous allons finir. Espérons que cette liste de techniques de moquerie sera en mesure de vous voir à travers même le code le plus complexe qui doit être testé. Faites-moi savoir ce que vous pensez sur Twitter.