• 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 comme side_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 typeExceptionlorsque nous en avons besoin, nous pouvons nous moquer de ressources spécifiques pour lancer une exception, puis utiliser pytest.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ètreside_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 contexte with, 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 contenu

se 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 module app.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’un MagicMock 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 donc made_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 lereturn_valuepour ê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 asyncio

    Commenç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:

    1. spec
    2. 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 balise AttributeError.,

    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 simple

    Le paramètrecreate=True 2d12c3cd62″> signifie que le mock.MagicMock retourné créera automatiquement tous les attributs appelés sur la maquette (c’est parce que la fonction open 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.