Je n'ai pas utilisé correctement la bibliothèque fictive auparavant, alors j'ai fait quelques recherches.
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
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()
>>>
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.
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