Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Mock it right! A beginner’s guide to world of tests and mocks, Maciej Polańczyk

3,718 views

Published on

PyParis 2017
http://pyparis.org

Published in: Technology
  • There are over 16,000 woodworking plans that comes with step-by-step instructions and detailed photos, Click here to take a look ☞☞☞ https://t.cn/A62Ygslz
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • D0WNL0AD FULL ▶ ▶ ▶ ▶ http://1lite.top/hsp0p ◀ ◀ ◀ ◀
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Before I came across this program, my confidence was a little low. Having achieved a 'C' grade in a GCSE mock exam just prior to it, I wasn't sure how I was going to better this. However, thanks to Jeevan's program, I was able to see how I could maximise my grade in a maths exam. At the end of the day, this is the most important thing � the result on the paper! After a month of following Jeevan's program, I achieved a strong 'B' grade (a couple of marks off an A) in my next GCSE mock exam. I achieved the highest grade in my entire year group and I am now being considered to take my GCSE maths exam in November this year, where I would have just begun year 11. My teacher was surprised; how did I make such a big improvement in maths in such a short space of time?!◆◆◆ https://bit.ly/33W8jmf
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • I've found your course really helpful and it's saved a lot of time so I can focus on my other subjects too such as English and Science. Your course has taught me a number of techniques to solve questions quicker. For instance, how to quickly turn recurring decimals into fractions. This has saved a lot of time when completing my mock exams. Thanks Jeevan. Your course is great! ★★★ http://t.cn/AirrSv7D
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Be the first to like this

Mock it right! A beginner’s guide to world of tests and mocks, Maciej Polańczyk

  1. 1. Mock it right! A beginner’s guide to world of tests and mocks. Maciej Polańczyk maciej.polanczyk@stxnext.pl Mock, Patch
  2. 2. Schedule - Class to be tested - Basic test - Mock - Not the best mock - The best mock - spec_set - Good mock - spec - Mock requests.post - Patch - Patch.object - Patch & import - Patch - raise Exception - Patch - different return_value - Patch - method instead of return_value - Patch - nesting patches - Patch - TestCase - Patch - in setUp - Patch - environment variables https://docs.python.org/3/library/unittest.mock.html
  3. 3. Class to be tested import requests class MessageSender: _max_attempts = 3 def send(self, message): data = { 'from': message.sender, 'to': message.receiver, 'subject': message.subject, } for attempt in range(self._max_attempts): response = requests.post(url='http://example.com/email', data=data) if response.status_code == requests.codes.created: return True return False
  4. 4. Basic test from message_sender import MessageSender class MessageSenderTests(unittest.TestCase): def test_should_send_message(): # GIVEN message_sender = MessageSender() message_to_be_sent = self._get_mocked_message() # WHEN result = message_sender.send(message_to_be_sent) # THEN self.assertTrue(result) # assert correct arguments were passed to request.post
  5. 5. Mock Use mock when you can pass it
  6. 6. Mock - creation from unittest.mock import Mock # mock and attributes mock = Mock() print(mock.not_existing_field) <Mock name='mock.not_existing_field' id='4327526528'> from unittest.mock import Mock # mock and methods mock = Mock() print(mock.not_existing_method()) <Mock name='mock.not_existing_method()' id='4372615856'>
  7. 7. Mock - creation from unittest.mock import Mock # mock and attributes mock = Mock() mock.existing_field = 5 print(mock.existing_field) 5 from unittest.mock import Mock # mock and methods mock = Mock() mock.existing_method.return_value = 5 print(mock.existing_method()) 5
  8. 8. Mock - creation from unittest.mock import Mock # mock and attributes mock = Mock(existing_field=5) print(mock.existing_field) 5 from unittest.mock import Mock # mock and methods mock = Mock(existing_method=Mock(return_value=5)) print(mock.existing_method()) 5
  9. 9. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock.existing_method() # in test method: mock.existing_method.assert_called_once_with()
  10. 10. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock.existing_method(1) # in test method: mock.existing_method.assert_called_once_with(1)
  11. 11. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock.existing_method(1) mock.existing_method(2) # in test method: mock.existing_method.assert_any_call(1)
  12. 12. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock(3) mock.existing_method(1) mock.existing_method(2) print(mock.call_args_list) output from print: [call(3)]
  13. 13. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock(3) mock.existing_method(1) mock.existing_method(2) print(mock.method_calls) output from print: [call.existing_method(1), call.existing_method(2)]
  14. 14. Mock - assertions from unittest.mock import Mock # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock(3) mock.existing_method(1) mock.existing_method(2) print(mock.mock_calls) output from print: [call(3), call.existing_method(1), call.existing_method(2)]
  15. 15. Mock - assertions from unittest.mock import Mock, call # in test method: mock = Mock() mock.existing_method.return_value = 5 # in source code: mock(3) mock.existing_method(1) mock.existing_method(2) # in test method: self.assertEqual(mock.mock_calls, [call(3), call.existing_method(1), call.existing_method(2)]) self.assertEqual(mock.existing_method.mock_calls, [call(1), call(2)])
  16. 16. Not the best mock from unittest.mock import Mock @staticmethod def _get_mocked_message(): message_mock = Mock( receiver='maciej.polanczyk@stxnext.pl', sender='maciej.polanczyk@test.com', subject='testing sending message' ) return message_mock
  17. 17. The best mock - spec_set from unittest.mock import Mock from message import Message @staticmethod def _get_mocked_message(): message_mock = Mock( spec_set=Message, receiver='maciej.polanczyk@stxnext.pl', sender='maciej.polanczyk@test.com', subject='testing sending message' ) return message_mock
  18. 18. Message class class Message: receiver = None sender = None subject = None
  19. 19. Message class def Message: def __init__(self, receiver, sender, subject): self.receiver = receiver self.sender = sender self.subject = subject
  20. 20. Good mock - spec import requests from unittest.mock import Mock @staticmethod def _get_success_response(): response_mock = Mock( spec=requests.Response, status_code=requests.codes.created ) return response_mock
  21. 21. Good mock - spec import requests from unittest.mock import Mock @staticmethod def _get_failure_response(): response_mock = Mock( spec=requests.Response, status_code=requests.codes.bad ) return response_mock
  22. 22. Basic test from message_sender import MessageSender class MessageSenderTests(unittest.TestCase): def test_should_send_message(): # GIVEN message_sender = MessageSender() message_to_be_sent = self._get_mocked_message() # WHEN result = message_sender.send(message_to_be_sent) # THEN self.assertTrue(result) # assert correct arguments were passed to request.post
  23. 23. Mock request.post import requests class MessageSender: _max_attempts = 3 def send(self, message): data = { 'from': message.sender, 'to': message.receiver, 'subject': message.subject, } for attempt in range(self._max_attempts): response = requests.post(url='http://example.com/email', data=data) if response.status_code == requests.codes.created: return True return False
  24. 24. Mock request.post class MessageSender: _max_attempts = 3 def __init__(): self._sender = ... def send(self, message): ... response = self._sender.post( url='http://example.com/email', data=data, ) ... class MessageSenderTests(unittest.TestCase): def test_should_send_message(self): # GIVEN message_sender = MessageSender() message_sender._sender = self._get_mocked_post_method() ... @staticmethod def _get_mocked_post_method(): post_method_mock = Mock( # spec_set=SenderClass return_value=MessageSenderTests._get_success_response() ) return post_method_mock
  25. 25. Patch Use patch when you can not pass mock
  26. 26. Patch from unittest.mock import patch from message_sender import MessageSender @patch('requests.post', autospec=True) def test_should_send_message(self, post_mock): # GIVEN post_mock.return_value = self._get_success_response() ... # THEN self.assertTrue(result) post_mock.assert_called_once_with( data={ 'from': 'maciej.polanczyk@test.com', ... }, url='http://example.com/email' )
  27. 27. Patch.object import requests from unittest.mock import patch from message_sender import MessageSender @patch.object(requests, 'post', autospec=True) def test_should_send_message(self, post_mock): # GIVEN post_mock.return_value = self._get_success_response() ... # THEN self.assertTrue(result) post_mock.assert_called_once_with( data={ ... }, url='http://example.com/email' )
  28. 28. Patch & imports from requests import post ... def send(self, message): ... response = post( url='http://example.com/email', data=data, ) ... from unittest.mock import patch import message_sender from message_sender import MessageSender @patch.object(message_sender, 'post', autospec=True) def test_should_send_message(self, post_mock): ... import requests ... def send(self, message): ... response = requests.post( url='http://example.com/email', data=data, ) … import requests from unittest.mock import patch from message_sender import MessageSender @patch.object(requests, 'post', autospec=True) def test_should_send_message(self, post_mock): ...
  29. 29. Patch - raise Exception import requests class MessageSender: _max_attempts = 3 def send(self, message): data = { 'from': message.sender, 'to': message.receiver, 'subject': message.subject, } for attempt in range(self._max_attempts): response = requests.post(url='http://example.com/email', data=data) if response.status_code == requests.codes.created: return True return False
  30. 30. Patch - raise Exception @patch.object(requests, 'post') def test_should_not_catch_exception(self, post_mock): # GIVEN post_mock.side_effect = RequestException( 'Expected exception from unit tests' ) message_sender = MessageSender() message_to_be_sent = self._get_example_message() # WHEN & THEN with self.assertRaisesRegex(RequestException, 'Expected exception'): message_sender.send(message_to_be_sent) post_mock.assert_called_once_with( data={ ... }, url='http://example.com/email' )
  31. 31. Patch - different return_value import requests class MessageSender: _max_attempts = 3 def send(self, message): data = { 'from': message.sender, 'to': message.receiver, 'subject': message.subject, } for attempt in range(self._max_attempts): response = requests.post(url='http://example.com/email', data=data) if response.status_code == requests.codes.created: return True return False
  32. 32. Patch - different return_value @patch.object(requests, 'post', autospec=True) def test_should_retry_sending_when_incorrect_status_received(self, post_mock): # GIVEN post_mock.side_effect = [self._get_failure_response(), self._get_failure_response(), self._get_failure_response(),] message_to_be_sent = self._get_example_message() message_sender = MessageSender() # WHEN result = message_sender.send(message_to_be_sent) # THEN self.assertFalse(result) expected_calls = [self._get_expected_call(), self._get_expected_call(), self._get_expected_call(),] self.assertEqual(post_mock.call_args_list, expected_calls) def _get_expected_call(self): return call( data = { 'from': 'maciej.polanczyk@test.com', 'subject': 'testing sending message', 'to': 'maciej.polanczyk@stxnext.pl' }, url = 'http://example.com/email' )
  33. 33. Patch - method instead of return_value @patch.object(requests, 'post', autospec=True) def test_should_send_message_tooo(self, post_mock): # GIVEN def implementation_from_unit_test(*args, **kwargs): return self._get_success_response() post_mock.side_effect = implementation_from_unit_test ...
  34. 34. Patch - nesting patches @patch.object(requests, 'post', autospec=True) @patch.object(requests, 'get', autospec=True) @patch.object(requests, 'put', autospec=True) def test_should_send_message_tooo(self, put_mock, get_mock, post_mock): # GIVEN ... # WHEN ... # THEN ...
  35. 35. Patch - TestCase class MessageSenderTests(unittest.TestCase): @patch.object(requests, 'post', autospec=True) def test_should_send_message(self, post_mock): pass @patch.object(requests, 'post', autospec=True) def test_should_not_catch_exception(self, post_mock): pass @patch.object(requests, 'post', autospec=True) class MessageSenderTests(unittest.TestCase): def test_should_send_message(self, post_mock): pass def test_should_not_catch_exception(self, post_mock): pass
  36. 36. Patch - in setUp class MessageSenderTests(unittest.TestCase): def setUp(self): super().setUp() patcher = patch('requests.post') self.addCleanup(patcher.stop) self._post_mock = patcher.start() def test_should_send_message(self): # GIVEN self._post_mock.return_value = self._get_success_response() # WHEN ... # THEN ...
  37. 37. Patch - environment variables class MessageSenderTests(unittest.TestCase): @patch.dict('os.environ', {'not_existing_key': 'some_value'}, clear=True) def test_should_override_environment_variables(self): self.assertEqual(os.environ, {'not_existing_key': 'some_value'})
  38. 38. Summary Use mock when you can pass it Use patch when you can not pass mock
  39. 39. Thank you! Questions?

×