Comprendre la bibliothèque d'objets Python Mock – Real Python

By | avril 11, 2019

Cours Python en ligne

Lorsque vous écrivez du code robuste, des tests sont essentiels pour vérifier que la logique de votre application est correcte, fiable et efficace. Cependant, la valeur de vos tests dépend de leur capacité à démontrer ces critères. Des obstacles tels que la logique complexe et les dépendances imprévisibles rendent difficiles la rédaction de tests utiles. La bibliothèque d'objets Python Mock, unittest.mock, peut vous aider à surmonter ces obstacles.

À la fin de cet article, vous pourrez:

  • Créez des objets fictifs Python à l'aide de Moquer
  • Affirmez que vous utilisez les objets comme vous le souhaitez
  • Inspectez les données d'utilisation stockées sur vos modèles Python
  • Configurez certains aspects de vos objets fictifs Python
  • Remplacez vos objets réels par des objets réels en utilisant pièce()
  • Évitez les problèmes courants inhérents à la moquerie de Python

Vous commencerez par voir ce qu’il en est de se moquer et comment il améliorera vos tests.

Qu'est-ce que se moquer?

Un objet fictif substitue et imite un objet réel dans un environnement de test. C'est un outil puissant et polyvalent pour améliorer la qualité de vos tests.

Une des raisons d’utiliser des objets fictifs Python est de contrôler le comportement de votre code lors des tests.

Par exemple, si votre code crée des requêtes HTTP vers des services externes, vos tests s'exécutent de manière prévisible uniquement dans la mesure où les services se comportent comme prévu. Parfois, une modification temporaire du comportement de ces services externes peut entraîner des défaillances intermittentes au sein de votre suite de tests.

Pour cette raison, il serait préférable que vous testiez votre code dans un environnement contrôlé. Le remplacement de la demande réelle par un objet fictif vous permettrait de simuler de manière prévisible les pannes de service externes et les réponses correctes.

Parfois, il est difficile de tester certaines zones de votre base de code. Ces zones comprennent sauf des blocs et si déclarations difficiles à satisfaire. L'utilisation d'objets fictifs Python peut vous aider à contrôler le chemin d'exécution de votre code pour atteindre ces zones et à améliorer la couverture de votre code.

Une autre raison d'utiliser des objets fictifs est de mieux comprendre comment vous utilisez leurs équivalents réels dans votre code. Un objet fictif Python contient des données sur son utilisation que vous pouvez inspecter, telles que:

  • Si vous avez appelé une méthode
  • Comment vous avez appelé la méthode
  • Combien de fois avez-vous appelé la méthode

Comprendre ce que fait un objet fictif est la première étape pour apprendre à en utiliser un.

Vous allez maintenant voir comment utiliser les objets fictifs Python.

La bibliothèque de maquette en python

La bibliothèque d'objets Mock Python est unittest.mock. Il fournit un moyen facile d’introduire des simulacres dans vos tests.

unittest.mock fournit une classe appelée Moquer que vous utiliserez pour imiter des objets réels dans votre base de code. Moquer offre une flexibilité incroyable et des données perspicaces. Ceci, avec ses sous-classes, répondra à la plupart des besoins moqueurs Python auxquels vous ferez face lors de vos tests.

La bibliothèque fournit également une fonction, appelée pièce(), qui remplace les objets réels dans votre code avec Moquer les instances. Vous pouvez utiliser pièce() en tant que décorateur ou gestionnaire de contexte, vous permettant de contrôler la portée dans laquelle l'objet sera simulé. Une fois que le périmètre désigné est sorti, pièce() nettoie votre code en remplaçant les objets simulés par leurs homologues d'origine.

Finalement, unittest.mock fournit des solutions à certains problèmes inhérents aux objets moqueurs.

Maintenant, vous comprenez mieux ce que sont les moqueries et la bibliothèque que vous utiliserez pour le faire. Laissez-vous plonger et explorer quelles fonctionnalités et fonctionnalités unittest.mock des offres.

le Moquer Objet

unittest.mock offre une classe de base pour les objets moqueurs appelés Moquer. Les cas d'utilisation de Moquer sont pratiquement illimités parce que Moquer est si flexible.

Commencez par instancier une nouvelle Moquer exemple:

>>>

>>> de unittest.mock importation Moquer
>>> moquer = Moquer()
>>> moquer

Maintenant, vous pouvez substituer un objet dans votre code avec votre nouveau Moquer. Vous pouvez le faire en le passant comme argument à une fonction ou en redéfinissant un autre objet:

# Passer le mock comme argument à do_something ()
faire quelque chose(moquer)

# Corrige la librairie json
JSON = moquer

Lorsque vous substituez un objet dans votre code, le Moquer doit ressembler à l'objet réel qu'il remplace. Sinon, votre code ne pourra pas utiliser le Moquer à la place de l'objet d'origine.

Par exemple, si vous vous moquez de la JSON bibliothèque et vos appels de programme décharges (), votre objet fictif Python doit également contenir décharges ().

Ensuite, vous verrez comment Moquer traite de ce défi.

Attributs et méthodes paresseux

UNE Moquer doit simuler tout objet qu’il remplace. Pour obtenir une telle flexibilité, il crée ses attributs lorsque vous y accédez:

>>>

>>> moquer.un_attribut

>>> moquer.faire quelque chose()

Puisque Moquer peut créer des attributs arbitraires à la volée, il convient de remplacer n’importe quel objet.

En utilisant un exemple d’avant, si vous vous moquez de la JSON bibliothèque et vous appelez décharges (), l’objet fictif Python créera la méthode afin que son interface puisse correspondre à celle de la bibliothèque:

>>>

>>> JSON = Moquer()
>>> JSON.décharges()

Notez deux caractéristiques principales de cette version simulée de décharges ():

  1. Contrairement au réel décharges (), cette méthode simulée ne nécessite aucun argument. En fait, il acceptera tous les arguments que vous lui transmettez.

  2. La valeur de retour de décharges () est aussi un Moquer. La capacité de Moquer définir récursivement d’autres simulacres vous permet de les utiliser dans des situations complexes:

>>>

>>> JSON = Moquer()
>>> JSON.charges('"k": "v").obtenir('k')

Parce que la valeur de retour de chaque méthode simulée est aussi un Moquer, vous pouvez utiliser vos simulacres de multiples façons.

Les simulacres sont flexibles, mais ils sont aussi informatifs. Vous apprendrez ensuite comment utiliser des simulacres pour mieux comprendre votre code.

Assertions et inspection

Moquer Les instances stockent des données sur la manière dont vous les avez utilisées. Par exemple, vous pouvez voir si vous avez appelé une méthode, comment vous avez appelé la méthode, etc. Il y a deux manières principales d'utiliser cette information.

Premièrement, vous pouvez affirmer que votre programme a utilisé un objet comme prévu:

>>>

>>> de unittest.mock importation Moquer

>>> # Créer un objet fictif
... JSON = Moquer()

>>> JSON.charges('"valeur clé"')


>>> # Vous savez que vous avez appelé charges () pour pouvoir
>>> # faire des affirmations pour tester cette attente
... JSON.charges.assert_called()
>>> JSON.charges.assert_called_once()
>>> JSON.charges.assert_called_with('"valeur clé"')
>>> JSON.charges.assert_called_once_with('"valeur clé"')

>>> JSON.charges('"valeur clé"')


>>> # Si une assertion échoue, la maquette émettra une erreur AssertionError.
... JSON.charges.assert_called_once()
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
  
  
  
  Fichier "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", ligne 795, dans assert_called_once
    élever AssertionError(msg)
AssertionError: Les «charges» attendues ont été appelées une fois. Appelé 2 fois.

>>> JSON.charges.assert_called_once_with('"valeur clé"')
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
  
  
  
  Fichier "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", ligne 824, dans assert_called_once_with
    élever AssertionError(msg)
AssertionError: Les charges attendues ne doivent être appelées qu'une seule fois. Appelé 2 fois.

>>> JSON.charges.assert_not_called()
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
  
  
  
  Fichier "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", ligne 777, dans assert_not_called
    élever AssertionError(msg)
AssertionError: Les «charges» attendues n'ont pas été appelées. Appelé 2 fois.

.assert_called () vous assure appelé la méthode moquée en .assert_called_once () vérifie que vous avez appelé la méthode exactement une fois.

Les deux fonctions d'assertion ont des variantes qui vous permettent d'inspecter les arguments transmis à la méthode simulée:

  • .assert_called_with (* arguments, ** kwargs)
  • .assert_called_once_with (* arguments, ** kwargs)

Pour passer ces assertions, vous devez appeler la méthode simulée avec les mêmes arguments que vous transmettez à la méthode réelle:

>>>

>>> JSON = Moquer()
>>> JSON.charges(s='"valeur clé"')
>>> JSON.charges.assert_called_with('"valeur clé"')
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
  
  
  
  Fichier "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", ligne 814, dans assert_called_with
    élever AssertionError(_Message d'erreur()) de cause
AssertionError: Appel attendu: charges ('"clé": "valeur"')
Appel effectif: charges (s = '"clé": "valeur"')
>>> JSON.charges.assert_called_with(s='"valeur clé"')

json.loads.assert_called_with ('"clé": "valeur"') soulevé un AssertionError parce qu'il s'attendait à ce que vous appeliez charges() avec un argument de position, mais vous l'avez appelé avec un argument de mot-clé. json.loads.assert_called_with (s = '"clé": "valeur"') obtient cette assertion correcte.

Deuxièmement, vous pouvez afficher des attributs spéciaux pour comprendre comment votre application a utilisé un objet:

>>>

>>> de unittest.mock importation Moquer

>>> # Créer un objet fictif
... JSON = Moquer()
>>> JSON.charges('"valeur clé"')


>>> # Nombre de fois que vous avez appelé charges ():
... JSON.charges.call_count
1
>>> # Le dernier appel de charges ():
... JSON.charges.call_args
call ('"clé": "valeur"')
>>> # Liste des appels load ():
... JSON.charges.call_args_list
[call('"key": "value"')]
>>> # Liste des appels aux méthodes de json (récursivement):
... JSON.appels_méthode
[call.loads('"key": "value"')]

Vous pouvez écrire des tests en utilisant ces attributs pour vous assurer que vos objets se comportent comme prévu.

Maintenant, vous pouvez créer des simulacres et inspecter leurs données d'utilisation. Ensuite, vous verrez comment personnaliser les méthodes simulées afin qu’elles deviennent plus utiles dans votre environnement de test.

Gérer la valeur de retour d'une maquette

Une des raisons d’utiliser mock est de contrôler le comportement de votre code pendant les tests. Une façon de faire est de spécifier la valeur de retour d’une fonction. Prenons un exemple pour voir comment cela fonctionne.

Tout d’abord, créez un fichier appelé mon_calendrier.py. Ajouter is_weekday (), une fonction qui utilise Python date / heure bibliothèque pour déterminer si oui ou non est un jour de semaine. Enfin, écrivez un test qui affirme que la fonction fonctionne comme prévu:

de date / heure importation date / heure

def is_weekday():
    aujourd'hui = date / heure.aujourd'hui()
    La bibliothèque datetime de # Python considère lundi comme 0 et dimanche comme étant 6
    revenir (0 <= aujourd'hui.jour de la semaine() < 5)

# Test si aujourd'hui est un jour de semaine
affirmer is_weekday()

Puisque vous testez si aujourd'hui est un jour de semaine, le résultat dépend du jour où vous exécutez votre test:

Si cette commande ne produit aucune sortie, l'assertion a réussi. Malheureusement, si vous exécutez la commande un week-end, vous aurez un AssertionError:

$ python mon_calendrier.py
Traceback (dernier appel le plus récent):
        Fichier "test.py", ligne 9, dans 
                assert is_weekday ()
AssertionError

Lors de la rédaction des tests, il est important de s'assurer que les résultats sont prévisibles. Vous pouvez utiliser Moquer pour éliminer les incertitudes de votre code lors des tests. Dans ce cas, vous pouvez vous moquer date / heure et mettre le .retour_value pour .aujourd'hui() à un jour que vous choisissez:

importation date / heure
de unittest.mock importation Moquer

# Enregistrez quelques jours de test
Mardi = date / heure.date / heure(année=2019, mois=1, journée=1)
samedi = date / heure.date / heure(année=2019, mois=1, journée=5)

# Mock datetime pour contrôler la date d'aujourd'hui
date / heure = Moquer()

def is_weekday():
    aujourd'hui = date / heure.date / heure.aujourd'hui()
    La bibliothèque datetime de # Python considère lundi comme 0 et dimanche comme étant 6
    revenir (0 <= aujourd'hui.jour de la semaine() < 5)

# Mock .today () pour revenir mardi
date / heure.date / heure.aujourd'hui.valeur_retour = Mardi
# Test mardi est un jour de semaine
affirmer is_weekday()
# Mock .today () pour revenir samedi
date / heure.date / heure.aujourd'hui.valeur_retour = samedi
# Test Samedi n'est pas un jour de semaine
affirmer ne pas is_weekday()

Dans l'exemple, .aujourd'hui() est une méthode moquée. Vous avez supprimé cette incohérence en attribuant un jour spécifique à la simulation. .retour_value. De cette façon, quand tu appelles .aujourd'hui(), il retourne le date / heure que vous avez spécifié.

Dans le premier test, vous vous assurez Mardi est un jour de semaine. Dans le deuxième test, vous vérifiez que samedi n'est pas un jour de semaine. Maintenant, peu importe le jour où vous exécutez vos tests car vous vous êtes moqués date / heure et avoir le contrôle sur le comportement de l'objet.

Lors de la construction de vos tests, vous rencontrerez probablement des cas où se moquer de la valeur de retour d’une fonction ne sera pas suffisant. En effet, les fonctions sont souvent plus compliquées qu'un simple flux de logique à sens unique.

Parfois, vous voudrez que les fonctions renvoient des valeurs différentes lorsque vous les appelez plusieurs fois, voire que des exceptions sont déclenchées. Vous pouvez le faire en utilisant .effet secondaire.

Gérer les effets secondaires d'une maquette

Vous pouvez contrôler le comportement de votre code en spécifiant les effets secondaires d’une fonction simulée. UNE .effet secondaire définit ce qui se passe lorsque vous appelez la fonction simulée.

Pour tester son fonctionnement, ajoutez une nouvelle fonction à mon_calendrier.py:

importation demandes

def get_holidays():
    r = demandes.obtenir('http: // localhost / api / holidays')
    si r.status_code == 200:
        revenir r.JSON()
    revenir Aucun

get_holidays () fait une demande au localhost serveur pour une série de vacances. Si le serveur répond avec succès, get_holidays () retournera un dictionnaire. Sinon, la méthode retournera Aucun.

Vous pouvez tester comment get_holidays () répondra à un délai de connexion en définissant requests.get.side_effect.

Pour cet exemple, vous ne verrez que le code correspondant à mon_calendrier.py. Vous allez créer un scénario de test en utilisant Python Test de l'unité bibliothèque:

importation Test de l'unité
de requests.exceptions importation Temps libre
de unittest.mock importation Moquer

# Simulez des requêtes pour contrôler son comportement
demandes = Moquer()

def get_holidays():
    r = demandes.obtenir('http: // localhost / api / holidays')
    si r.status_code == 200:
        revenir r.JSON()
    revenir Aucun

classe TestCalendar(Test de l'unité.Cas de test):
    def test_get_holidays_timeout(soi):
        # Tester un délai de connexion
        demandes.obtenir.effet secondaire = Temps libre
        avec soi.assertRaises(Temps libre):
            get_holidays()

si __prénom__ == '__principale__':
    Test de l'unité.principale()

Tu utilises .assertRaises () pour vérifier que get_holidays () soulève une exception étant donné le nouvel effet secondaire de obtenir().

Exécutez ce test pour voir le résultat de votre test:

$ python mon_calendrier.py
.
-------------------------------------------------- -----
Ran 1 test sur 0.000s

D'accord

Si vous voulez être un peu plus dynamique, vous pouvez définir .effet secondaire à une fonction qui Moquer invoquera lorsque vous appelez votre méthode simulée. La maquette partage les arguments et la valeur de retour de la .effet secondaire une fonction:

importation demandes
importation Test de l'unité
de unittest.mock importation Moquer

# Simulez des requêtes pour contrôler son comportement
demandes = Moquer()

def get_holidays():
    r = demandes.obtenir('http: // localhost / api / holidays')
    si r.status_code == 200:
        revenir r.JSON()
    revenir Aucun

classe TestCalendar(Test de l'unité.Cas de test):
    def log_request(soi, url):
        # Enregistrer une fausse demande à des fins de test
        impression(F'Faire une demande à url')
        impression('Demande reçue!')

        # Créer une nouvelle maquette pour imiter une réponse
        response_mock = Moquer()
        response_mock.status_code = 200
        response_mock.JSON.valeur_retour = 
            '12 / 25 ': 'Noël',
            '7/4': 'Le jour de l'indépendance',
        
        revenir response_mock

    def test_get_holidays_logging(soi):
        # Tester une requête enregistrée avec succès
        demandes.obtenir.effet secondaire = soi.log_request
        affirmer get_holidays()[[[['12 / 25 '] == 'Noël'

si __prénom__ == '__principale__':
    Test de l'unité.principale()

Tout d'abord, vous avez créé .log_request (), qui prend une URL, enregistre une sortie en utilisant impression(), puis retourne un Moquer réponse. Ensuite, vous définissez la .effet secondaire de obtenir() à .log_request (), que vous utiliserez lorsque vous appelez get_holidays (). Lorsque vous exécutez votre test, vous verrez que obtenir() transmet ses arguments à .log_request () accepte alors la valeur de retour et la renvoie également:

$ python mon_calendrier.py
Faire une demande à http: // localhost / api / holidays.
Demande reçue!
.
-------------------------------------------------- -----
Ran 1 test sur 0.000s

D'accord

Génial! le impression() les instructions ont enregistré les valeurs correctes. Également, get_holidays () retourné le dictionnaire de vacances.

.effet secondaire peut aussi être un iterable. L'itéré doit consister en des valeurs de retour, des exceptions ou un mélange des deux. L'itérable produira sa valeur suivante chaque fois que vous appelez votre méthode fausse. Par exemple, vous pouvez vérifier qu’une nouvelle tentative après une Temps libre renvoie une réponse réussie:

importation Test de l'unité
de requests.exceptions importation Temps libre
de unittest.mock importation Moquer

# Simulez des requêtes pour contrôler son comportement
demandes = Moquer()

def get_holidays():
    r = demandes.obtenir('http: // localhost / api / holidays')
    si r.status_code == 200:
        revenir r.JSON()
    revenir Aucun

classe TestCalendar(Test de l'unité.Cas de test):
    def test_get_holidays_retry(soi):
        # Créer une nouvelle maquette pour imiter une réponse
        response_mock = Moquer()
        response_mock.status_code = 200
        response_mock.JSON.valeur_retour = 
            '12 / 25 ': 'Noël',
            '7/4': 'Le jour de l'indépendance',
        
        # Définir l'effet secondaire de .get ()
        demandes.obtenir.effet secondaire = [[[[Temps libre, response_mock]
        # Test que la première requête déclenche un Timeout
        avec soi.assertRaises(Temps libre):
            get_holidays()
        # Réessayez maintenant, dans l’attente d’une réponse positive
        affirmer get_holidays()[[[['12 / 25 '] == 'Noël'
        # Enfin, assert .get () a été appelé deux fois
        affirmer demandes.obtenir.call_count == 2

si __prénom__ == '__principale__':
    Test de l'unité.principale()

La première fois que vous appelez get_holidays (), obtenir() soulève un Temps libre. La deuxième fois, la méthode retourne un dictionnaire de vacances valide. Ces effets secondaires correspondent à l'ordre dans lequel ils apparaissent dans la liste passée à .effet secondaire.

Vous pouvez définir .retour_value et .effet secondaire sur un Moquer directement. Toutefois, puisqu'un objet fictif Python doit être flexible dans la création de ses attributs, il existe un meilleur moyen de configurer ces paramètres, ainsi que d'autres.

Configurer votre maquette

Vous pouvez configurer un Moquer définir certains comportements de l’objet. Certains membres configurables incluent .effet secondaire, .retour_value, et .prénom. Vous configurez un Moquer lorsque vous en créez un ou que vous utilisez .configure_mock ().

Vous pouvez configurer un Moquer en spécifiant certains attributs lors de l'initialisation d'un objet:

>>>

>>> moquer = Moquer(effet secondaire=Exception)
>>> moquer()
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
  
  
  
  Fichier "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", ligne 939, dans __appel__
    revenir _mock_self._mock_call(*args, **Kwargs)
  Fichier "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", ligne 995, dans _mock_call
    élever effet
Exception

>>> moquer = Moquer(prénom=«Vraie maquette en python»)
>>> moquer


>>> moquer = Moquer(valeur_retour=Vrai)
>>> moquer()
Vrai

Tandis que .effet secondaire et .retour_value peut être réglé sur le Moquer par exemple, lui-même, d'autres attributs comme .prénom ne peut être réglé que par .__ init __ () ou .configure_mock (). Si vous essayez de définir le .prénom du Moquer sur le cas, vous obtiendrez un résultat différent:

>>>

>>> moquer = Moquer(prénom=«Vraie maquette en python»)
>>> moquer.prénom


>>> moquer = Moquer()
>>> moquer.prénom = «Vraie maquette en python»
>>> moquer.prénom
«Vraie maquette en python»

.prénom est un attribut commun pour les objets à utiliser. Alors, Moquer ne vous permet pas de définir cette valeur sur l’instance de la même manière que vous le pouvez avec .retour_value ou .effet secondaire. Si vous accédez mock.name vous allez créer un .prénom attribuer au lieu de configurer votre maquette.

Vous pouvez configurer un existant Moquer en utilisant .configure_mock ():

>>>

>>> moquer = Moquer()
>>> moquer.configure_mock(valeur_retour=Vrai)
>>> moquer()
Vrai

En décompressant un dictionnaire dans .configure_mock () ou Mock .__ init __ (), vous pouvez même configurer les attributs de votre objet fictif Python. En utilisant Moquer configurations, vous pouvez simplifier un exemple précédent:

# Verbose, old Mock
response_mock = Moquer()
response_mock.JSON.valeur_retour = 
    '12 / 25 ': 'Noël',
    '7/4': 'Le jour de l'indépendance',


# Shiny, new .configure_mock ()
vacances = '12 / 25 ': 'Noël', '7/4': 'Le jour de l'indépendance'
response_mock = Moquer(**'json.return_value': vacances)

Maintenant, vous pouvez créer et configurer des objets fictifs Python. Vous pouvez également utiliser des simulacres pour contrôler le comportement de votre application. Jusqu'à présent, vous avez utilisé des simulacres comme arguments de fonctions ou des objets de correction dans le même module que vos tests.

Ensuite, vous apprendrez à substituer vos simulacres à des objets réels dans d’autres modules.

pièce()

unittest.mock fournit un mécanisme puissant pour les objets moqueurs, appelé pièce(), qui recherche un objet dans un module donné et remplace cet objet par un Moquer.

Habituellement, vous utilisez pièce() en tant que décorateur ou gestionnaire de contexte pour fournir une étendue dans laquelle vous allez vous moquer de l'objet cible.

pièce() en tant que décorateur

Si vous souhaitez simuler un objet pendant toute la durée de votre fonction de test, vous pouvez utiliser pièce() en tant que décorateur de fonction.

Pour voir comment cela fonctionne, réorganisez votre mon_calendrier.py fichier en mettant la logique et les tests dans des fichiers séparés:

importation demandes
de date / heure importation date / heure

def is_weekday():
    aujourd'hui = date / heure.aujourd'hui()
    La bibliothèque datetime de # Python considère lundi comme 0 et dimanche comme étant 6
    revenir (0 <= aujourd'hui.jour de la semaine() < 5)

def get_holidays():
    r = demandes.obtenir('http: // localhost / api / holidays')
    si r.status_code == 200:
        revenir r.JSON()
    revenir Aucun

Ces fonctions sont maintenant dans leur propre fichier, séparés de leurs tests. Ensuite, vous allez recréer vos tests dans un fichier nommé tests.py.

Jusque-là, vous avez des objets patchés dans le fichier dans lequel ils se trouvent. La correction de singe est le remplacement d'un objet par un autre lors de l'exécution. Maintenant, vous utiliserez pièce() pour remplacer vos objets dans mon_calendrier.py:

importation Test de l'unité
de mon_calendrier importation get_holidays
de requests.exceptions importation Temps libre
de unittest.mock importation pièce

classe TestCalendar(Test de l'unité.Cas de test):
    @pièce('mon_calendrier.requêtes')
    def test_get_holidays_timeout(soi, mock_requests):
            mock_requests.obtenir.effet secondaire = Temps libre
            avec soi.assertRaises(Temps libre):
                get_holidays()
                mock_requests.obtenir.assert_called_once()

si __prénom__ == '__principale__':
    Test de l'unité.principale()

A l'origine, vous avez créé un Moquer et patché demandes dans la portée locale. Maintenant, vous devez accéder à la demandes bibliothèque en mon_calendrier.py de tests.py.

Pour ce cas, vous avez utilisé pièce() en tant que décorateur et passé le chemin de l'objet cible. Le chemin cible était 'mon_calendrier.requêtes' qui se compose du nom du module et de l'objet.

Vous avez également défini un nouveau paramètre pour la fonction de test. pièce() utilise ce paramètre pour passer l'objet simulé dans votre test. À partir de là, vous pouvez modifier la maquette ou faire des assertions si nécessaire.

Vous pouvez exécuter ce module de test pour vous assurer qu’il fonctionne comme prévu:

$ python tests.py
.
-------------------------------------------------- -----
A couru 1 test en 0.001s

D'accord

En utilisant pièce() en tant que décorateur a bien fonctionné dans cet exemple. Dans certains cas, il est plus lisible, plus efficace ou plus facile à utiliser pièce() en tant que gestionnaire de contexte.

pièce() en tant que gestionnaire de contexte

Parfois, vous voudrez utiliser pièce() en tant que gestionnaire de contexte plutôt que décorateur. Certaines des raisons pour lesquelles vous pourriez préférer un gestionnaire de contexte sont les suivantes:

  • Vous voulez seulement simuler un objet pour une partie de la portée du test.
  • Vous utilisez déjà trop de décorateurs ou de paramètres, ce qui nuit à la lisibilité de votre test.

Utiliser pièce() en tant que gestionnaire de contexte, vous utilisez avec déclaration:

importation Test de l'unité
de mon_calendrier importation get_holidays
de requests.exceptions importation Temps libre
de unittest.mock importation pièce

classe TestCalendar(Test de l'unité.Cas de test):
    def test_get_holidays_timeout(soi):
        avec pièce('mon_calendrier.requêtes') comme mock_requests:
            mock_requests.obtenir.effet secondaire = Temps libre
            avec soi.assertRaises(Temps libre):
                get_holidays()
                mock_requests.obtenir.assert_called_once()

si __prénom__ == '__principale__':
    Test de l'unité.principale()

Lorsque le test se termine le avec déclaration, pièce() remplace l'objet simulé par l'original.

Jusqu'à présent, vous avez simulé des objets complets, mais parfois vous ne voulez que vous moquer d'une partie d'un objet.

Corriger les attributs d'un objet

Supposons que vous ne vouliez vous moquer que d’une méthode d’un objet au lieu de tout l’objet. Vous pouvez le faire en utilisant patch.object ().

Par exemple, .test_get_holidays_timeout () vraiment seulement besoin de se moquer requests.get () et mettre sa .effet secondaire à Temps libre:

importation Test de l'unité
de mon_calendrier importation demandes, get_holidays
de unittest.mock importation pièce

classe TestCalendar(Test de l'unité.Cas de test):
    @pièce.objet(demandes, 'obtenir', effet secondaire=demandes.exceptions.Temps libre)
    def test_get_holidays_timeout(soi, mock_requests):
            avec soi.assertRaises(demandes.exceptions.Temps libre):
                get_holidays()

si __prénom__ == '__principale__':
    Test de l'unité.principale()

Dans cet exemple, vous vous êtes moqué de obtenir() plutôt que tous demandes. Tous les autres attributs restent les mêmes.

objet() prend les mêmes paramètres de configuration que pièce() Est-ce que. Mais au lieu de passer le chemin de la cible, vous fournissez l’objet cible, lui-même, en tant que premier paramètre. Le deuxième paramètre est l'attribut de l'objet cible que vous essayez de simuler. Vous pouvez aussi utiliser objet() en tant que gestionnaire de contexte comme pièce().

Apprendre à utiliser pièce() est critique pour le moquage d'objets dans d'autres modules. Cependant, le chemin de l’objet cible n’est parfois pas évident.

Où patcher

Savoir où dire pièce() Il est important de rechercher l'objet que vous voulez simuler, car si vous choisissez le mauvais emplacement cible, le résultat de pièce() pourrait être quelque chose que vous ne vous attendiez pas.

Disons que vous vous moquez is_weekday () dans mon_calendrier.py en utilisant pièce():

>>>

>>> importation mon_calendrier
>>> de unittest.mock importation pièce

>>> avec pièce('my_calendar.is_weekday'):
...     mon_calendrier.is_weekday()
...

Tout d'abord, vous importez mon_calendrier.py. Ensuite, vous patch is_weekday ()en le remplaçant par un Moquer. Génial! Cela fonctionne comme prévu.

Maintenant, changeons légèrement cet exemple et importons directement la fonction:

>>>

>>> de mon_calendrier importation is_weekday
>>> de unittest.mock importation pièce

>>> avec pièce('my_calendar.is_weekday'):
...     is_weekday()
...
Faux

Notez que même si l’emplacement cible que vous avez indiqué est pièce() n'a pas changé, le résultat de l'appel is_weekday () est différent. La différence est due au changement dans la manière dont vous avez importé la fonction.

from my_calendar import is_weekday lie la fonction réelle à la portée locale. Donc, même si vous pièce() la fonction plus tard, vous ignorez la maquette car vous avez déjà une référence locale à la fonction non-mockée.

Une bonne règle est de pièce() l'objet où il est levé.

Dans le premier exemple, se moquant 'my_calendar.is_weekday ()' fonctionne parce que vous recherchez la fonction dans le mon_calendrier module. Dans le deuxième exemple, vous avez une référence locale à is_weekday (). Puisque vous utilisez la fonction trouvée dans la portée locale, vous devriez vous moquer de la fonction locale:

>>>

>>> de unittest.mock importation pièce
>>> de mon_calendrier importation is_weekday

>>> avec pièce('__main __. is_weekday'):
...     is_weekday()
...

Maintenant, vous avez une compréhension ferme du pouvoir de pièce(). Vous avez vu comment pièce() les objets et les attributs, ainsi que l'endroit où les corriger.

Ensuite, vous verrez quelques problèmes communs inhérents au moquage d’objets et aux solutions unittest.mock fournit.

Problèmes moqueurs courants

Les objets moqueurs peuvent introduire plusieurs problèmes dans vos tests. Certains problèmes sont inhérents aux moqueries alors que d’autres sont spécifiques aux unittest.mock. N'oubliez pas qu'il existe d'autres problèmes de moquage qui ne sont pas mentionnés dans ce didacticiel.

Ceux qui sont abordés ici se ressemblent en ce sens que le problème qu’ils posent est fondamentalement identique. Dans chaque cas, les assertions du test ne sont pas pertinentes. Bien que l'intention de chaque simulacre soit valable, les simulacres eux-mêmes ne le sont pas.

Modifications apportées aux interfaces d'objet et fautes d'orthographe

Les classes et les définitions de fonctions changent tout le temps. Lorsque l'interface d'un objet change, tout test reposant sur un Moquer de cet objet peut devenir sans importance.

Par exemple, vous renommez une méthode mais vous oubliez qu’un test la moque et appelle .assert_not_called (). Après le changement, .assert_not_called () est encore Vrai. L'assertion n'est toutefois pas utile car la méthode n'existe plus.

Des tests non pertinents peuvent ne pas sembler critiques, mais s’ils sont vos seuls tests et que vous supposez qu’ils fonctionnent correctement, la situation pourrait être désastreuse pour votre application.

Un problème spécifique à Moquer est-ce une faute d'orthographe peut casser un test. Rappelons qu'un Moquer crée son interface lorsque vous accédez à ses membres. Ainsi, vous créerez par inadvertance un nouvel attribut si vous épelez son nom par erreur.

Si vous appelez .asert_called () au lieu de .assert_called (), votre test ne soulève pas de AssertionError. C’est parce que vous avez créé une nouvelle méthode sur l’objet fictif Python nommé .asert_called () au lieu d'évaluer une affirmation réelle.

Ces problèmes se produisent lorsque vous modifiez des objets dans votre propre base de code. Un problème différent se pose lorsque vous modifiez des objets en interaction avec des bases de code externes.

Changements apportés aux dépendances externes

Imaginez à nouveau que votre code envoie une demande à une API externe. Dans ce cas, la dépendance externe est l'API susceptible de changer sans votre consentement.

D'une part, les tests unitaires testent des composants isolés du code. Ainsi, en se moquant du code qui fait la demande, vous pouvez tester vos composants isolés dans des conditions contrôlées. Cependant, cela pose aussi un problème potentiel.

Si une dépendance externe change d’interface, vos objets fictifs Python ne seront plus valides. Si cela se produit (et que le changement d'interface est radical), vos tests réussiront parce que vos objets fictifs ont masqué le changement, mais votre code de production échouera.

Malheureusement, ce n'est pas un problème qui unittest.mock fournit une solution pour. Vous devez faire preuve de jugement lorsque vous vous moquez des dépendances externes.

Ces trois problèmes peuvent entraîner des problèmes de pertinence des tests et des problèmes potentiellement coûteux, car ils menacent l'intégrité de vos simulacres. unittest.mock vous donne quelques outils pour faire face à ces problèmes.

Eviter les problèmes courants avec les spécifications

Comme indiqué précédemment, si vous modifiez une définition de classe ou de fonction ou si vous orthographiez mal un attribut d'objet fictif Python, vous pouvez rencontrer des problèmes lors de vos tests.

Ces problèmes se produisent parce que Moquer crée des attributs et des méthodes lorsque vous y accédez. La réponse à ces problèmes est de prévenir Moquer de créer des attributs qui ne sont pas conformes à l’objet que vous essayez de vous moquer.

Lors de la configuration d'un Moquer, you can pass an object specification to the spec paramètre. le spec parameter accepts a list of names or another object and defines the mock’s interface. If you attempt to access an attribute that does not belong to the specification, Mock will raise an AttributeError:

>>>

>>> de unittest.mock importation Mock
>>> calendrier = Mock(spec=[[[['is_weekday', 'get_holidays'])

>>> calendrier.is_weekday()

>>> calendrier.create_event()
Traceback (most recent call last):
  File "", line 1, dans 
  
  
  
  File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, dans __getattr__
    élever AttributeError("Mock object has no attribute %r" % prénom)
AttributeError: Mock object has no attribute 'create_event'

Here, you’ve specified that calendrier has methods called .is_weekday() et .get_holidays(). When you access .is_weekday(), it returns a Mock. When you access .create_event(), a method that does not match the specification, Mock raises an AttributeError.

Specifications work the same way if you configure the Mock with an object:

>>>

>>> importation my_calendar
>>> de unittest.mock importation Mock

>>> calendrier = Mock(spec=my_calendar)
>>> calendrier.is_weekday()

>>> calendrier.create_event()
Traceback (most recent call last):
  File "", line 1, dans 
  
  
  
  File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, dans __getattr__
    élever AttributeError("Mock object has no attribute %r" % prénom)
AttributeError: Mock object has no attribute 'create_event'

.is_weekday() is available to calendrier because you configured calendrier pour correspondre à la my_calendar module’s interface.

En outre, unittest.mock provides convenient methods of automatically specifying a Mock instance’s interface.

One way to implement automatic specifications is create_autospec:

>>>

>>> importation my_calendar
>>> de unittest.mock importation create_autospec

>>> calendrier = create_autospec(my_calendar)
>>> calendrier.is_weekday()

>>> calendrier.create_event()
Traceback (most recent call last):
  File "", line 1, dans 
  
  
  
  File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, dans __getattr__
    élever AttributeError("Mock object has no attribute %r" % prénom)
AttributeError: Mock object has no attribute 'create_event'

Like before, calendrier est un Mock instance whose interface matches my_calendar. Si vous utilisez patch(), you can send an argument to the autospec parameter to achieve the same result:

>>>

>>> importation my_calendar
>>> de unittest.mock importation pièce

>>> avec pièce('__main__.my_calendar', autospec=True) comme calendrier:
...     calendrier.is_weekday()
...     calendrier.create_event()
...

Traceback (most recent call last):
  File "", line 1, dans 
  
  
  
  File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, dans __getattr__
    élever AttributeError("Mock object has no attribute %r" % prénom)
AttributeError: Mock object has no attribute 'create_event'

Conclusion

You’ve learned so much about mocking objects using unittest.mock!

Now, you’re able to:

  • Use Mock to imitate objects in your tests
  • Check usage data to understand how you use your objects
  • Customize your mock objects’ return values and side effects
  • patch() objects throughout your codebase
  • See and avoid problems with using Python mock objects

You have built a foundation of understanding that will help you build better tests. You can use mocks to gain insights into your code that you would not have been able to get otherwise.

I leave you with one final disclaimer. Beware of overusing mock objects!

It’s easy to take advantage of the power of Python mock objects and mock so much that you actually decrease the value of your tests.

If you’re interested in learning more about unittest.mock, I encourage you to read its excellent documentation.