Je suis nouveau sur Python, donc je m'excuse s'il s'agit d'une question en double ou trop simple. J'ai écrit une classe coordinatrice qui appelle deux autres classes qui utilisent la bibliothèque kafka-python pour envoyer/lire des données depuis Kafka. Je veux écrire un test unitaire pour ma classe de coordinateur, mais j'ai du mal à trouver la meilleure façon de procéder. J'espérais pouvoir créer un autre constructeur dans lequel je pourrais passer mes objets simulés, mais cela ne semble pas fonctionner car j'obtiens une erreur dans laquelle test_mycoordinator ne peut pas être résolu. Vais-je tester cette classe dans le mauvais sens? Existe-t-il un moyen Pythonique de le tester?
Voici à quoi ressemble ma classe de test jusqu'à présent:
import unittest
from mock import Mock
from mypackage import mycoordinator
class MyTest(unittest.TestCase):
def setUpModule(self):
# Create a mock producer
producer_attributes = ['__init__', 'run', 'stop']
mock_producer = Mock(name='Producer', spec=producer_attributes)
# Create a mock consumer
consumer_attributes = ['__init__', 'run', 'stop']
data_out = [{u'dataObjectID': u'test1'},
{u'dataObjectID': u'test2'},
{u'dataObjectID': u'test3'}]
mock_consumer = Mock(
name='Consumer', spec=consumer_attributes, return_value=data_out)
self.coor = mycoordinator.test_mycoordinator(mock_producer, mock_consumer)
def test_send_data(self):
# Create some data and send it to the producer
count = 0
while count < 3:
count += 1
testName = 'test' + str(count)
self.coor.sendData(testName , None)
Et voici la classe que j'essaie de tester:
class MyCoordinator():
def __init__(self):
# Process Command Line Arguments using argparse
...
# Initialize the producer and the consumer
self.myproducer = producer.Producer(self.servers,
self.producer_topic_name)
self.myconsumer = consumer.Consumer(self.servers,
self.consumer_topic_name)
# Constructor used for testing -- DOES NOT WORK
@classmethod
def test_mycoordinator(cls, mock_producer, mock_consumer):
cls.myproducer = mock_producer
cls.myconsumer = mock_consumer
# Send the data to the producer
def sendData(self, data, key):
self.myproducer.run(data, key)
# Receive data from the consumer
def getData(self):
data = self.myconsumer.run()
return data
Il n'est pas nécessaire de fournir un constructeur distinct. Mocking corrige votre code pour remplacer les objets par des mocks. Utilisez simplement le mock.patch()
décorateur sur vos méthodes de test; il transmettra des références aux faux objets générés.
producer.Producer()
et consumer.Consumer()
sont ensuite simulés avant de créer l'instance:
import mock
class MyTest(unittest.TestCase):
@mock.patch('producer.Producer', autospec=True)
@mock.patch('consumer.Consumer', autospec=True)
def test_send_data(self, mock_consumer, mock_producer):
# configure the consumer instance run method
consumer_instance = mock_consumer.return_value
consumer_instance.run.return_value = [
{u'dataObjectID': u'test1'},
{u'dataObjectID': u'test2'},
{u'dataObjectID': u'test3'}]
coor = MyCoordinator()
# Create some data and send it to the producer
for count in range(3):
coor.sendData('test{}'.format(count) , None)
# Now verify that the mocks have been called correctly
mock_producer.assert_has_calls([
mock.Call('test1', None),
mock.Call('test2', None),
mock.Call('test3', None)])
Ainsi, au moment où test_send_data
Est appelé, le code mock.patch()
remplace la référence producer.Producer
Par un objet factice. Votre classe MyCoordinator
utilise alors ces objets fictifs plutôt que le vrai code. l'appel de producer.Producer()
renvoie un nouvel objet simulé (le même objet auquel mock_producer.return_value
fait référence), etc.
J'ai fait l'hypothèse que producer
et consumer
sont des noms de module de niveau supérieur. Si ce n'est pas le cas, indiquez le chemin d'importation complet. Dans la documentation mock.patch()
:
target doit être une chaîne sous la forme
'package.module.ClassName'
. La cible est importée et l'objet spécifié remplacé par le nouvel objet, de sorte que la cible doit être importable à partir de l'environnement à partir duquel vous appelezpatch()
. La cible est importée lorsque la fonction décorée est exécutée, pas au moment de la décoration.