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

Mocks: Successfully isolating the snake

Mocks: Successfully isolating the snake

Ever wonder what mocks, fakes, dummies stubs or spies are? Or what the differences are between them and when one should be used instead of another? We will take a deep look into these concepts, what they mean and examples on how to use/create them in Python.

In this talk, we will quickly explore the reasons for using mock and how it works, quickly jumping into the different concepts of testing doubles and how they can be used in Python. The talk will include as part of the examples from some features in mock coming in Python 3.7 that might change how usual mocking is performed. The talk builds on the writings of Gerard Meszaros and Martin Fowler about testing doubles, focusing on how to apply them to Python.

This session, which will review test isolation concepts and the unittest.mock module, is structured in a way that both beginners and intermediate developers will learn from it. A basic knowledge of testing is recommended. Intermediate developers will leave the room with a clear understanding of the tools - further than just using simple mocks - to successfully fake dependencies. Multiple “not so well known” features of unittest.mock will be presented so we can shape those objects to behave functionally different. Unittest.mock is an extremely useful library which is commonly underused, this talk aims to bring clarity into stubbing in general and into medium/advanced mock features to ease and remove pain when users work with it.

Mario Corchero

July 26, 2018
Tweet

More Decks by Mario Corchero

Other Decks in Programming

Transcript

  1. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Mocks, dummies, stubs & spies: Successfully isolating the snake EuroPython 2018 July 26, 2018 Mario Corchero Senior Software Developer @mariocj89
  2. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. What are testing doubles? An object that looks like the real one but the creator is under control of its behavior.
  3. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Why we need testing doubles def predict_department_expenses(budget): ...
  4. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. How to create testing doubles unittest.mock.Mock() class MarioDouble(): @property def nationality(self): return "Spanish" def is_allowed_to_live_in_uk(self): return not BREXIT_ENABLED
  5. © 2018 Bloomberg Finance L.P. All rights reserved. Filling parameters

    A function takes a parameter you just want to ignore. def predict_department_expenses(budget, email_subject): ... assert EXPECTED_RESULT == predict_department_expenses(INPUT_BUDGET, None) assert EXPECTED_RESULT == predict_department_expenses(INPUT_BUDGET, “hihi”) Known as Dummy
  6. © 2018 Bloomberg Finance L.P. All rights reserved. Simulating behaviour

    fake = Mock(return_value="SUCCESS", is_enabled=True) def predict_department_expenses(budget, email_sender): ... assert email_sender.is_enabled email_sender(...) ... Known as Stub
  7. © 2018 Bloomberg Finance L.P. All rights reserved. fake =

    Mock() Known as Stub Simulating behaviour def predict_department_expenses(budget, email_sender): ... assert email_sender.is_enabled email_sender(...) ...
  8. © 2018 Bloomberg Finance L.P. All rights reserved. Simulating magic

    methods fake = MagicMock() def predict_department_expenses(budget, email_sender): ... count_mails_sent = email_sender.send_email(...) total_mails_sent += count_mails_sent ... Known as Stub
  9. © 2018 Bloomberg Finance L.P. All rights reserved. More complex

    behaviour stub = Mock(return_value=1) def predict_department_expenses(budget, email_sender): ... count_mails_sent = email_sender(...) if count_mails_sent == 0: raise HorribleException(f"{email_sender} did not send emails") ... stub = Mock(return_value=0) stub = Mock(side_effect=[2, 10, 20, 0]) stub = Mock(side_effect=Exception(“Failed”)) stub = Mock(side_effect=print) Known as Stub
  10. © 2018 Bloomberg Finance L.P. All rights reserved. Changing internal

    dependencies import email_service email_sender = email_service.Sender() def predict_department_expenses(budget): ... count_mails_sent = email_sender(...) ... with unittest.mock.patch('module.to.test.email_sender') as stub: stub.return_value = 1 module.to.test.predict_department_expenses(budget) Known as Stub
  11. © 2018 Bloomberg Finance L.P. All rights reserved. Verifying interactions

    def predict_department_expenses(budget, email_sender): ... sent_count = email_sender.send_mail(...) if not sent_count: raise Exception("Failed to send emails") ... mock = MagicMock() mock.send_email.return_value = 3 predict_department_expenses(budget) mock.send_email.assert_called_with(SUBJECT, to=EMAIL) Known as Mock
  12. © 2018 Bloomberg Finance L.P. All rights reserved. Using spec

    mock = Mock(spec=mailing) mock.send_email() # raises AttributeError Known as Mock
  13. © 2018 Bloomberg Finance L.P. All rights reserved. Using seal

    def predict_department_expenses(budget, email_sender): ... sent_count = email_sender.send_mail(...).response[0].data ... mock = MagicMock() email_sender.send_mail(...).response[0].payload = “{}” predict_department_expenses(budget, mock) mock.assert_called() Known as Mock
  14. © 2018 Bloomberg Finance L.P. All rights reserved. Using seal

    def predict_department_expenses(budget, email_sender): ... sent_count = email_sender.send_mail(...).response[0].data ... mock = MagicMock() email_sender.send_mail(...).response[0].data = “{}” seal(email_sender) predict_department_expenses(budget, mock) mock.assert_called() Known as Mock
  15. © 2018 Bloomberg Finance L.P. All rights reserved. def predict_department_expenses(budget,

    email_sender): ... email_sender.send_email(...) ... spy = Mock(wraps=email_sender) assert spy.called args, kwargs = spy.call_args subject, destination = args Known as Spy Inspect interaction of a real object
  16. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Wrap up & conclusions • Names: Dummies, Fakes, Stubs, Spies & Mocks • Unittest.mock helps us create them • Patch can be used to use testing doubles on internal dependencies • Use spec or seal to freeze your Mock instances • Wraps allows you to easily create spies
  17. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Take away
  18. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Extra content!
  19. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. sentinels def predict_department_expenses(budget): ... email_sender.send_email(budget) ... from unittest.mock import sentinel @unittest.mock.patch('module.to.test.email_sender', autospec=True) def test_case(mock): predict_department_expenses(“A LOT OF MONEY”) mock.assert_called_with(“A LOT OF MONEY”)
  20. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. sentinels def predict_department_expenses(budget): ... email_sender.send_email(budget) ... from unittest.mock import sentinel @unittest.mock.patch('module.to.test.email_sender', autospec=True) def test_case(mock): predict_department_expenses(sentinel.budget) mock.assert_called_with(sentinel.budget)
  21. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. How does patch work? • Temporary replace your object with another object • By default: a new MagicMock • Just a setattr 'package.module.target' Path to set Attr to set
  22. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. How does patch work? #package/module.py target = real_object 'package.module.target' Path to set Attr to set
  23. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. How does patch work? #package/module.py target = real_object 'package.module.target' Path to set Attr to set # myfile.py from package.module import target print(target)
  24. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. How does patch work? #package/module.py target = real_object # myfile.py from package.module import target print(target) 'myfile.target' Path to set Attr to set
  25. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Mock fixtures import os from unittest.mock import patch import pytest @pytest.fixture def os_system_mock(): with patch('os.system') as os_system: os_system.return_value = 1 yield os_system def test_calling_os(os_system_mock): os.system("echo 1")
  26. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Naming your mocks def printer(a, b): print(a if unknown_variable else b) printer(Mock(), Mock()) # <Mock id='140469819538008'>
  27. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Naming your mocks >>> Mock(name="mymock") <Mock name='mymock' ='139811286151008'> >>> Mock(name="mymock").many.chained().calls <Mock name='mymock.many.chained().calls' ='139811274838536'>
  28. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Go to https://kahoot.it/ CODE: change screen! Kahoot!
  29. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Links • https://docs.python.org/3/library/unittest.mock.html • http://xunitpatterns.com/Test%20Double.html • https://martinfowler.com/bliki/TestDouble.html • https://pyvideo.org/pycon-us-2018/demystifying-the-patch-function.html • https://github.com/python/cpython/blob/master/Lib/unittest/mock.py
  30. © 2018 Bloomberg Finance L.P. All rights reserved. © 2018

    Bloomberg Finance L.P. All rights reserved. Questions? Thanks