J'ai un fichier python file a.py
qui contient deux classes A
et B
.
class A(object):
def method_a(self):
return "Class A method a"
class B(object):
def method_b(self):
a = A()
print a.method_a()
Je voudrais unittest method_b
dans la classe B
en se moquant A
. Voici le contenu du fichier testa.py
dans ce but:
import unittest
import mock
import a
class TestB(unittest.TestCase):
@mock.patch('a.A')
def test_method_b(self, mock_a):
mock_a.method_a.return_value = 'Mocked A'
b = a.B()
b.method_b()
if __== '__main__':
unittest.main()
Je m'attends à avoir Mocked A
dans la sortie. Mais ce que je reçois c'est:
<MagicMock name='A().method_a()' id='4326621392'>
Où est-ce que je me trompe?
Lorsque vous @mock.patch('a.A')
, vous remplacez la classe A
dans le code testé par mock_a
.
Dans B.method_b
, Vous définissez ensuite a = A()
, qui est maintenant a = mock_a()
- c'est-à-dire a
est le return_value
De mock_a
. Comme vous n'avez pas spécifié cette valeur, il s'agit d'un MagicMock
normal; ce n'est pas configuré non plus, donc vous obtenez la réponse par défaut (encore un autre MagicMock
) lorsque vous appelez des méthodes dessus.
Au lieu de cela, vous voulez configurer le return_value
De mock_a
pour avoir la méthode appropriée, que vous pouvez faire soit:
mock_a().method_a.return_value = 'Mocked A'
# ^ note parentheses
ou peut-être plus explicitement:
mock_a.return_value.method_a.return_value = 'Mocked A'
Votre code aurait fonctionné dans le cas a = A
(Assigner la classe, pas créer une instance), car alors a.method_a()
aurait déclenché votre méthode fictive.
Je préfère pytest avec mocker fixture . Voici le même test, en utilisant pytest et mocker:
import a
class TestB:
def test_method_b(self, mocker):
mock_A = mocker.MagicMock(name='A', spec=a.A)
mocker.patch('a.A', new=mock_A)
mock_A.return_value.method_a.return_value = 'Mocked A'
b = a.B()
b.method_b()
Vous trouverez peut-être que la façon dont j'ai écrit le test est plus intéressante que le test lui-même - j'ai créé une bibliothèque python pour m'aider à utiliser la syntaxe.
Voici comment j'ai abordé votre problème de manière systématique:
Nous commençons par le test que vous voulez et ma bibliothèque d'assistance:
import a
from mock_autogen.pytest_mocker import PytestMocker
class TestB:
def test_method_b(self, mocker):
# this would output the mocks we need
print(PytestMocker(a).mock_classes().prepare_asserts_calls().generate())
# your original test, without the mocks
b = a.B()
b.method_b()
Maintenant, le test ne fait pas grand chose, mais la sortie imprimée est utile:
# mocked classes
mock_A = mocker.MagicMock(name='A', spec=a.A)
mocker.patch('a.A', new=mock_A)
mock_B = mocker.MagicMock(name='B', spec=a.B)
mocker.patch('a.B', new=mock_B)
# calls to generate_asserts, put this after the 'act'
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_A, name='mock_A'))
print(mock_autogen.generator.generate_asserts(mock_B, name='mock_B'))
Maintenant, je mets une seule maquette pour A
avant l'appel à B()
et la section generate_asserts
Après, comme cela (pas besoin de la précédente impression, donc je enlevé):
def test_method_b(self, mocker):
# mocked classes
mock_A = mocker.MagicMock(name='A', spec=a.A)
mocker.patch('a.A', new=mock_A)
# your original test, without the mocks
b = a.B()
b.method_b()
# calls to generate_asserts, put this after the 'act'
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_A, name='mock_A'))
Après cette exécution de test, nous avons obtenu de précieuses contributions:
assert 1 == mock_A.call_count
mock_A.assert_called_once_with()
mock_A.return_value.method_a.assert_called_once_with()
mock_A.return_value.method_a.return_value.__str__.assert_called_once_with()
Les deux premières lignes vérifient que le A
mock a été initialisé une fois, sans paramètre. La troisième ligne vérifie que method_a
A été appelé, alors que la 4ème ligne peut être la plus utile pour vous et vous aurait peut-être permis de gagner beaucoup de temps.
mock_A.return_value.method_a.return_value.__str__.assert_called_once_with()
Vous voyez que la valeur renvoyée de method_a
A été appliquée avec str
(en raison de la fonction print
.). le remplacer par votre chaîne désirée est assez facile:
mock_A.return_value.method_a.return_value = 'Mocked A'
Et c’est comme ça que j’ai eu la méthode de test complète mentionnée ci-dessus.