Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Mocking en Python

Mocking en Python

Chilango Django #14

Henoc Díaz

June 28, 2018
Tweet

More Decks by Henoc Díaz

Other Decks in Programming

Transcript

  1. ¿Cómo creamos Mocks en Python? # Python < 3.3 import

    mock # Python >= 3.3 import unittest.mock @henocdz 8
  2. Instalación • Python < 3.3 pip install mock • Python

    >= 3.3 Integrado por default @henocdz 9
  3. unittest.mock.Mock Crea atributos y métodos conforme se acceden y almacena

    información sobre su uso. from unittest.mock import Mock mock = Mock() mock.return_value = 'Imaginémonos cosas chingonas!' print(mock()) >> Imaginémonos cosas chingonas! @henocdz 11
  4. side_effect Excepción, función o iterador que se ejecutará al momento

    de llamar al mock instance. mock.side_effect = lambda x: x mock.side_effect = ['h', 'o', 'l', 'a'] mock.side_effect = RuntimeError('x_x') @henocdz 13
  5. from unittest.mock import Mock spec_mock = Mock(spec=['foo']) print(spec_mock.bar) AttributeError: Mock

    object has no attribute 'bar' spec_mock.bar = 'Yeah! :)' print(spec_mock.bar) >> Yeah! :) @henocdz 16
  6. spec_set from unittest.mock import Mock from django.db.models import Model spec_set_mock

    = Mock(spec_set=Model) print(spec_set_mock.bar) AttributeError: Mock object has no attribute 'bar' print(spec_set_mock.pk) <Mock name='mock.pk' id='4426207184'> @henocdz 17
  7. asserts • assert_called • assert_called_once • assert_called_with • assert_called_once_with •

    assert_any_call • assert_has_calls • assert_not_called @henocdz 20
  8. unittest.mock.MagicMock Lo mismo que Mock pero incluye magic methods como:

    • __lt__: NotImplemented • __gt__: NotImplemented • __int__: 1 • __contains__: False • __len__: 0 @henocdz 21
  9. • __iter__: iter([]) • __float__: 1.0 • __bool__: True •

    __str__: default str for the mock e.g from unittest.mock import MagicMock mock = MagicMock() len(mock) >> 0 @henocdz 22
  10. @patch Nos permite parchar un objecto en particular durante la

    ejecución de la función o bloque with from unittest.mock import patch with patch('path.to.module.Class.method', autospec=True) as mocked_method: pass @henocdz 24
  11. # En mailer module class MailSvc(object): def __init__(self): self.client =

    MailGun() if settings.PRODUCTION else SES() def send_mail(cls, sender, recipients, *etc, **etcetc): response = self.client.send_mail(sender, recipients, *etc, **etcetc) if response.succeed: return True, recipients, response.message return False, [], response.message @henocdz 25
  12. # En applicant_service module from mailer import MailSvc mail_service =

    MailSvc() class ApplicantSvc(object): def invite_applicant(self, applicant_pk): applicant = Applicant.objects.get(pk=applicant_pk) applicant.invited = True applicant.save() mail_service.send_mail(subject='...', recipients=[applicant.email]...) @henocdz 26
  13. from unittest.mock import patch @patch('applicant_service.MailSvc.send_mail', autospec=True) def test_invite_applicant(self, mocked_send_mail): recipients

    = ['[email protected]'] mocked_send_mail.return_value = True, recipients, 'Email sent' applicant = applicant.objects.create(*dummy, **data) ApplicantSvc.invite_applicant(applicant_pk=applicant.pk) applicant.refresh_from_db() self.assertTrue(applicant.invited) mocked_send_mail.assert_called_once() @henocdz 27
  14. @patch.object # En applicant_service module class Applicant(models.Model): first_name = models.CharField(max_length=100)

    last_name = models.CharField(max_length=100) def index(self): # Update record in ElasticSearch pass def save(self, *args, **kwargs): super(Applicant, self).save(*args, **kwargs) self.index() @henocdz 30
  15. @patch.object class TestApplicantModel(TestCase): def test_index_is_called(self): with patch.object( Applicant, 'index', return_value=None

    ) as mocked_index: applicant = Applicant.objects.create( first_name='Chilango', last_name='Django' ) self.assertEqual(mocked_index.call_count, 0) applicant.last_name = 'Dev' applicant.save() self.assertEqual(mocked_index.call_count, 1) @henocdz 31
  16. # En some_package.some_module module def send_sms(phone, message): # Some expensive

    call to external service return True def send_invites(phones): sent_to = [] for phone in phones: sent = send_sms(phone=phone, message='Invitación...') if sent: sent_to.append(phone) return True, sent_to @henocdz 33
  17. from unittest import TestCase from unittest.mock import patch, call from

    some_package.some_module import send_invites class TestSendInvites(TestCase): @patch('demo_magicmock.some_module.send_sms', autospec=True) def test_send_invites(self, mocked_send_sms): _recipients = ['5564137890', '5564137891'] status, recipients = send_invites(_recipients) self.assertTrue(status) self.assertListEqual(_recipients, recipients) calls = ( call(phone='5564137890', message='Invitación...'), call(phone='5564137891', message='Invitación...') ) mocked_send_sms.assert_has_calls(calls, any_order=True) mocked_send_sms.imaginemonos_cosas_chingonas() @henocdz 34
  18. Primer opción @patch('some_package.requests.get') def test_httpretty_patch(self, mocked_get): mocked_get.return_value.status_code = 200 mocked_json_response

    = Mock() mocked_json_response.return_value = dict(foo='bar') mocked_get.return_value.json = mocked_json_response response = get_google() self.assertEqual(response.status_code, 200) self.assertDictEqual(response.json(), {"foo": "bar"}) @henocdz 37
  19. from unittest import TestCase import httpretty from some_package.httpretty_demo import get_google

    class TestHTTPretty(TestCase): @httpretty.activate def test_httpretty(self): httpretty.register_uri( httpretty.GET, 'https://google.com', body='{"foo": "bar"}' ) response = get_google() self.assertEqual(response.status_code, 200) self.assertDictEqual(response.json(), {"foo": "bar"}) @henocdz 38
  20. Factory Boy from factory import Sequence, SubFactory from factory.django import

    DjangoModelFactory from database.factories.address import AddressFactory class ApplicantFactory(DjangoModelFactory): class Meta: model = 'applicants.Applicant' address = SubFactory(AddressFactory) first_name = Sequence(lambda n: "applicant %d" % n) last_name = Sequence(lambda n: "las_name %d" % n) email = Sequence(lambda n: "applicant_%[email protected]" % n) mobile_phone = Sequence(lambda n: "556489679%d" % n) applicant = ApplicantFactory() @henocdz 39
  21. Faker from faker import Faker fake = Faker() fake.name() #

    'Lucy Cechtelar' fake.address() # "426 Jordy Lodge # Cartwrightshire, SC 88120-6700" fake.text() # Sint velit eveniet. Rerum atque repellat voluptatem quia rerum. Numquam excepturi... my_word_list = ['danish','cheesecake','sugar'] fake.sentence(ext_word_list=my_word_list) @henocdz 40
  22. Freezegun Let your Python tests travel through time from freezegun

    import freeze_time @freeze_time("2012-01-14 03:21:34", tz_offset=-4) def test(): assert datetime.datetime.utcnow() == datetime.datetime(2012, 1, 14, 3, 21, 34) assert datetime.datetime.now() == datetime.datetime(2012, 1, 13, 23, 21, 34) # datetime.date.today() uses local time assert datetime.date.today() == datetime.date(2012, 1, 13) @henocdz 41
  23. moto Mocks Boto3 import boto3 class MyModel(object): def __init__(self, name,

    value): self.name = name self.value = value def save(self): s3 = boto3.client('s3', region_name='us-east-1') s3.put_object(Bucket='mybucket', Key=self.name, Body=self.value) @henocdz 42
  24. import boto3 from moto import mock_s3 from mymodule import MyModel

    @mock_s3 def test_my_model_save(): conn = boto3.resource('s3', region_name='us-east-1') # We need to create the bucket since this is all in Moto's 'virtual' AWS account conn.create_bucket(Bucket='mybucket') model_instance = MyModel('steve', 'is awesome') model_instance.save() body = conn.Object('mybucket', 'steve').get()['Body'].read().decode("utf-8") assert body == b'is awesome' @henocdz 43