Une note sur mock (bibliothèque fictive Python)

Je n'ai pas utilisé correctement la bibliothèque fictive auparavant, alors j'ai fait quelques recherches.

installation simulée

Il existe plusieurs bibliothèques de simulation Python, mais cette fois, j'utiliserai mock. Dans Python 3.3 ou version ultérieure, il s'agit de Standard Library, et lors de l'écriture d'un programme qui ne prend en charge que 3.3 ou version ultérieure N'a pas besoin d'installer une bibliothèque externe. En fait, il semble qu'il y ait de nombreux cas où il n'est pas pris en charge seulement après la 3.3, alors sera-t-il courant d'installer avec pip?

installation simulée


$ pip install mock

Remplacer par une maquette

Le point est la classe mock.Mock. Une instance de la classe Mock peut être appelée et vous pouvez définir la valeur de retour lorsque vous appelez.

Réglage de la valeur de retour


>>> from mock import Mock
>>> m = Mock()
# return_Définissez la valeur de retour dans l'attribut value
>>> m.return_value = 5
>>> m()
5

Remplacez l'instance Mock par la valeur de retour définie de cette manière par la méthode de classe en cours de traitement. La plupart de ce que vous faites est ceci.

Un exemple est illustré à l'aide du code suivant. Dans le code ci-dessous, B # b_test dépend du traitement de A # a_test. Et si je veux tester B # b_test seul?

B#b_test et A#a_dépendance de test


class A(object):
    def a_test(self):
        print('test')

class B(object):
    def __init__(self, a_ins):
        self.a_ins = a_ins
    def b_test(self):
        return self.a_ins.a_test()

Dans ce cas, la dépendance peut être séparée en remplaçant A # a_test par l'instance Mock.

Remplacement pour simuler


>>> a = A()
>>> a.a_test = Mock()
>>> a.a_test.return_value = 'mocked'
>>> b = B(a)
>>> b.b_test()
'mocked'

Dans l'exemple ci-dessus, la méthode est remplacée, mais dans de nombreux cas, vous souhaitez remplacer la méthode entière sur une base d'instance, pas sur une base de méthode. Dans ce cas, lors de la création d'une instance Mock, spécifiez la classe à simuler dans l'argument spec.

Remplacement pour simuler(utiliser les spécifications)


>>> a = Mock(spec=A)
>>> a.a_test.return_value = 'mocked_spec'
>>> b = B(a)
>>> b.b_test()
'mocked_spec'

Les appels simulés sont enregistrés dans l'instance Mock. Vous pouvez utiliser ces informations pour vérifier les relations entre les instances (= si l'appel est correct).

Enregistrement de l'appel simulé


>>> a = Mock(spec=A)
>>> a.a_test.return_value = 'mocked_spec'
>>> b = B(a)
# Mock#call_args_list:Liste pour stocker les appels pour cette instance Mock
>>> a.a_test.call_args_list
[]

# Mock#assert_any_call:Indiquez si l'appel d'instance Mock correspondant était dans le passé.
#Dans cet exemple, il n'y a pas d'argument, mais en réalité il est possible de donner n'importe quel argument(Affirmez s'il y a eu un appel d'instance Mock avec cet argument)
# (ref. http://www.voidspace.org.uk/python/mock/mock.html)
>>> a.a_test.assert_any_call()
Traceback (most recent call last):
  File "<ipython-input-81-ed526bd5ddf7>", line 1, in <module>
    a.a_test.assert_any_call()
  File "/Users/tatsuro/.venv/py3.3/lib/python3.3/site-packages/mock.py", line 891, in assert_any_call
    '%s call not found' % expected_string
AssertionError: a_test() call not found

>>> b.b_test()
'mocked_spec'

# B#b_test()Appelé via
>>> a.a_test.call_args_list
[call()]
>>> a.a_test.assert_any_call()
>>>

Se moquer du processus de renvoi d'une exception

Si vous souhaitez retourner une valeur de retour, vous pouvez la définir sur return_value, mais que faire si vous voulez vous moquer du processus de levée d'une exception comme indiqué ci-dessous?

Traitement pour lever une exception


class A(object):
    def a_test(self):
        raise ValueError

class B(object):
    def __init__(self, a_ins):
        self.a_ins = a_ins
    def b_test(self):
        try:
            return self.a_ins.a_test()
        except ValueError:
            print('error handling')

Dans ce cas, utilisez Mock # side_effect. En définissant une exception ici, vous pouvez lever une exception lorsque vous appelez la simulation.

side_Traitement des exceptions simulé par effet


>>> a = Mock(spec=A)
>>> a.a_test.side_effect = ValueError
>>> b = B(a)
>>> b.b_test()
error handling

Ici, la gestion des exceptions est mentionnée comme un cas d'utilisation courant, mais side_effect n'est pas spécialisé pour la gestion des exceptions. C'est un mécanisme pour "ajouter la logique nécessaire lors de l'appel d'une instance Mock", et il est également possible d'ajouter du traitement uniquement à un argument spécifique et de l'utiliser comme valeur de retour comme indiqué ci-dessous.

Ajouter un traitement différent en fonction de l'argument


>>> class B(object):
    def __init__(self, a_ins):
        self.a_ins = a_ins
    def b_test(self, value):
        try:
            return self.a_ins.a_test(value)
        except ValueError:
            print('error handling')

>>> def handler(value):
    if (value % 2) == 0:
        return value * 2
    return value

>>> a.a_test.side_effect = handler
>>> b = B(a)
>>> b.b_test(1)
1
>>> b.b_test(2)
4
>>> b.b_test(3)
3

Cependant, incorporer une logique trop compliquée dans side_effect ne semble pas préférable du point de vue de la maintenance. Si la logique se complique, il peut être nécessaire de prendre des mesures telles que la division du scénario de test.

Activer la moquerie dans une étendue spécifique

mock a un mécanisme appelé patch qui permet de se moquer uniquement dans une portée spécifique. patch peut être utilisé dans les appels de fonction, mais il peut également être traité comme un gestionnaire de contexte ou un décorateur. Dans de nombreux cas, il sera traité ici.

Un exemple de gestion en tant que gestionnaire de contexte est le suivant. Vous pouvez gérer la maquette avec le nom spécifié après comme.

Traitez le patch comme un gestionnaire de contexte


>>> class C(object):
    def hoge(self):
        return 'hoge'

#Classe C simulée uniquement avec une portée sous l'instruction with(CMock)Peut être utilisé
>>> with patch('__main__.C') as CMock:
    c = CMock()
    c.hoge.return_value = 'test'
    print(c.hoge())
...
test

Lorsqu'on le traite comme un décorateur, c'est comme suit. Un simulacre est passé comme dernier argument de la fonction décorée.

Traitez le patch comme un décorateur pour une fonction


>>> @patch('__main__.C')
    def patched_func(CMock):
        c = CMock()
        c.hoge.return_value = 'test'
        print(c.hoge())
...
>>> patched_func()
test

Il peut être traité non seulement comme un décorateur pour une fonction mais aussi comme un décorateur pour une classe. De nombreux outils de test ont la capacité de regrouper les cas de test en classes (en supposant = 1 cas de test / 1 méthode), de sorte qu'une simulation commune peut être appliquée à tous les tests. A ce moment, il faut faire attention à l'existence de patch.TEST_PREFIX. Si vous spécifiez un décorateur pour la classe, la simulation n'est transmise que pour les méthodes commençant par patch.TEST_PREFIX. Par conséquent, il est nécessaire de mettre en œuvre la méthode de test selon une certaine règle de dénomination. (Mais la valeur par défaut est "test", alors ne vous inquiétez pas trop?)

Traitez le patch comme un décorateur pour une classe


#Mock n'est transmis qu'aux méthodes avec ce préfixe.
>>> patch.TEST_PREFIX
'test'

>>> @patch('__main__.C')
class CTest(object):
    def test_c(self, CMock):
        c = CMock()
        c.hoge.return_value = 'hoge_test'
        print(c.hoge())

    def notmock(self, CMock):
        pass
...
# 'test'Méthodes qui commencent par. Un simulacre est remis.
>>> CTest().test_c()
hoge_test

# 'test'Une méthode qui ne commence pas par. Puisque le simulacre n'est pas passé, une erreur se produira en raison d'arguments insuffisants.
>>> CTest().notmock()
Traceback (most recent call last):
  File "<ipython-input-189-cee8cb83c7b4>", line 1, in <module>
    CTest().notmock()
TypeError: notmock() missing 1 required positional argument: 'CMock'

Recommended Posts

Une note sur mock (bibliothèque fictive Python)
Un mémorandum sur le simulacre de Python
Une note sur [python] __debug__
Python: une note sur les classes 1 "Résumé"
Une note sur __call__
Une note sur le sous-processus
Une note sur mprotect (2)
Écrire une note sur la version python de python virtualenv
Un mémorandum sur la bibliothèque de wrapper Python tesseract
Une note sur KornShell (ksh)
Mémorandum sur la corrélation [Python]
Une note sur TensorFlow Introduction
Utilisez pymol comme bibliothèque python
Remarque Python: à propos de la comparaison en utilisant is
Une note sur l'utilisation de l'API Facebook avec le SDK Python
Remarque à propos de get_scorer de sklearn
Remarque: Python
Note de Python
Générer un badge d'affichage du nombre de téléchargements de la bibliothèque Python
À propos de psd-tools, une bibliothèque capable de traiter des fichiers psd en Python
Après avoir fait des recherches sur la bibliothèque Python, j'ai un peu compris egg.info.
Une note où un débutant Python s'est retrouvé coincé
Un programmeur Java a étudié Python. (À propos du type)
À propos du 02 février 2020 * Ceci est un article Python.
Un mémo que j'ai essayé le tutoriel Pyramid
Bibliothèque de messagerie Python 3.6
À propos de la notation d'inclusion de python
Note d'apprentissage Python_002
Remarque: décorateur Python
Note de programmation Python
[Python] Note d'apprentissage 1
Bibliothèque Python AST
À propos de Python tqdm.
À propos du rendement Python
Note d'apprentissage Python_004
Essayez d'utiliser APSW, une bibliothèque Python que SQLite peut prendre au sérieux
À propos de l'héritage Python
À propos de python, range ()
Note d'apprentissage Python_003
Note sur la bibliothèque Python
À propos de Python Decorator
Une note sur les fonctions de la bibliothèque Linux standard qui gère le temps
[Remarque] openCV + python
Remarque sur awk
À propos de la référence Python
À propos des décorateurs Python
[Python] À propos du multi-processus
Note du débutant Python
Un programmeur Java a étudié Python. (À propos des fonctions (méthodes))
Un programmeur Java a étudié Python. (À propos des décorateurs)
[Note] Créez une classe de fuseau horaire sur une ligne avec python
Un mémo que j'ai écrit un tri de fusion en Python
Notes de programme simples Pub / Sub en Python
Remarque Python: lors de l'attribution d'une valeur à une chaîne
Une histoire sur l'exécution de Python sur PHP sur Heroku
Pensez à créer un environnement Python 3 dans un environnement Mac
[Note] À propos du rôle du trait de soulignement "_" en Python
Modificateurs de vérification des mémos avec MaxPlus
Une histoire sur la modification de Python et l'ajout de fonctions
À propos de Python for loop