Slide 1

Slide 1 text

Python Testing using Mock & PyTest

Slide 2

Slide 2 text

unittest.mock

Slide 3

Slide 3 text

unittest.mock Defn: unittest.mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used. • Using Mock you can replace/mock any dependency of your code. • Unreliable or expensive parts of code are mocked using Mock, e.g. Networks, Intensive calculations, posting on a website, system calls, etc.

Slide 4

Slide 4 text

• As a developer you want your calls to be right rather than going all the way to final output. • So to speed up your automated unit-tests you need to keep out slow code from your test runs.

Slide 5

Slide 5 text

Mock - Basics

Slide 6

Slide 6 text

>>> from unittest.mock import Mock >>> m = Mock() >>> m >>> m.some_value = 23 >>> m.some_value 23 >>> m.other_value Mock Objects - Basics

Slide 7

Slide 7 text

>>> m.get_value(value=42) >>> m.get_value.assert_called_once_with(value=42) >>> m.get_value.assert_called_once_with(value=2) raise AssertionError(_error_message()) from cause AssertionError: Expected call: get_value(value=2) Actual call: get_value(value=42)

Slide 8

Slide 8 text

• Flexible objects that can replace any part of code. • Creates attributes when accessed. • Records how objects are being accessed. • Using this history of object access you can make assertions about objects. More about Mock objects

Slide 9

Slide 9 text

>>> from unittest.mock import Mock >>> config = { ... 'company': 'Lenovo', ... 'model': 'Ideapad Z510', ... 'get_sticker_count.return_value': 11, ... 'get_fan_speed.side_effect': ValueError ... } >>> m = Mock(**config) >>> m.company 'Lenovo' >>> m.get_sticker_count() 11 >>> m.get_fan_speed() raise effect ValueError Customize mock objects

Slide 10

Slide 10 text

Using spec to define attr >>> user_info = ['first_name', 'last_name', 'email'] >>> m = Mock(spec=user_info) >>> m.first_name >>> m.address raise AttributeError("Mock object has no attribute % r" % name) AttributeError: Mock object has no attribute 'address'

Slide 11

Slide 11 text

Automatically create all specs >>> from unittest.mock import create_autospec >>> import os >>> m = create_autospec(os) >>> m. Display all 325 possibilities? (y or n) m.CLD_CONTINUED m.forkpty m.CLD_DUMPED m.fpathconf m.CLD_EXITED m.fsdecode [CUT] m.fchown m.walk m.fdatasync m.write m.fdopen m.writev m.fork

Slide 12

Slide 12 text

Using Mock through patch • Replaces a named object with Mock object • Also can be used as decorator/context manager that handles patching module and class level attributes within the scope of a test.

Slide 13

Slide 13 text

1 # main.py 2 import requests 3 import json 4 5 def upload(text): 6 try: 7 url = 'http://paste.fedoraproject.org/' 8 data = { 9 'paste_data': text, 10 'paste_lang': None, 11 'api_submit': True, 12 'mode': 'json' 13 } 14 reply = requests.post(url, data=data) 15 return reply.json() 16 except ValueError as e: 17 print("Error:", e) 18 return None 19 except requests.exceptions.ConnectionError as e: 20 print('Error:', e) 21 return None 22 except KeyboardInterrupt: 23 print("Try again!!") 24 return None 25 26 if __name__ == '__main__': 27 print(upload('Now in boilerplate'))

Slide 14

Slide 14 text

1 # tests.py 2 import unittest 3 import requests 4 from unittest.mock import patch 5 from main import upload 6 7 text = 'This is ran from a test case' 8 url = 'http://paste.fedoraproject.org/' 9 data = { 10 'paste_data': text, 11 'paste_lang': None, 12 'api_submit': True, 13 'mode': 'json' 14 } 15 class TestUpload(unittest.TestCase): 16 def test_upload_function(self): 17 with patch('main.requests') as mock_requests: 18 result = upload(text) # call our function 19 mock_requests.post.assert_called_once_with(url, data=data) 20 21 def test_upload_ValueError(self): 22 with patch('main.requests') as mock_requests: 23 mock_requests.post.side_effect = ValueError 24 result = upload(text) 25 mock_requests.post.assert_any_call(url, data=data) 26 self.assertEqual(result, None)

Slide 15

Slide 15 text

patching methods #1 >>> @patch('requests.Response') ... @patch('requests.Session') ... def test(session, response): ... assert session is requests.Session ... assert response is requests.Response ... >>> test()

Slide 16

Slide 16 text

patching methods #2 >>> with patch.object(os, 'listdir', return_value= ['abc.txt']) as mock_method: ... a = os.listdir('/home/hummer') ... >>> mock_method.assert_called_once_with ('/home/hummer') >>>

Slide 17

Slide 17 text

Mock return_value >>> m = Mock() >>> m.return_value = 'some random value 4' >>> m() 'some random value 4' OR >>> m = Mock(return_value=3) >>> m.return_value 3 >>> m() 3

Slide 18

Slide 18 text

Mock side_effect • This can be a Exception, Iterable or function. • If you pass in a function it will be called with same arguments as the mock, unless function returns DEFAULT singleton.

Slide 19

Slide 19 text

#1 side_effect for Exception >>> m = Mock() >>> m.side_effect = ValueError('You are always gonna get this!!') >>> m() raise effect ValueError: You are always gonna get this!!

Slide 20

Slide 20 text

>>> m = Mock() >>> m.side_effect = [1, 2, 3, 4] >>> m(), m(), m(), m() (1, 2, 3, 4) >>> m() StopIteration #2 side_effect for returning sequence of values

Slide 21

Slide 21 text

>>> m = Mock() >>> side_effect = lambda value: value ** 3 >>> m.side_effect = side_effect >>> m(2) 8 #3 side_effect as function

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

Installation For Python3 $ pip3 install -U pytest For Python2 $ pip install -U pytest or $ easy_install -U pytest

Slide 24

Slide 24 text

What is pytest? ● A fully featured Python Testing tool. ● Do automated tests.

Slide 25

Slide 25 text

Tests with less Boilerplate 1 import unittest 2 3 def cube(number): 4 return number ** 3 5 6 7 class Testing(unittest.TestCase): 8 def test_cube(self): 9 assert cube(2) == 8 Before py.test

Slide 26

Slide 26 text

1 def cube(number): 2 return number ** 3 3 4 def test_cube(): 5 assert cube(2) == 8 6 7 # Here no imports or no classes are needed After py.test

Slide 27

Slide 27 text

Running Tests pytest will run all files in the current directory and its subdirectories of the form test_*.py or *_test.py or else you can always feed one file at a time. $ py.test cube.py =============================== test session starts============================================ platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1 rootdir: /home/hummer/Study/Nov2015PythonPune/pyt, inifile: collected 1 items cube.py . ===============================1 passed in 0.01 seconds========================================

Slide 28

Slide 28 text

$ py.test Run entire test suite $ py.test test_bar.py Run all tests in a specific file $ py.test -k test_foo Run all the tests that are named test_foo By default pytest discovers tests in test_*.py and *_test.py

Slide 29

Slide 29 text

pytest fixtures • Fixtures are implemented in modular manner, as each fixture triggers a function call which in turn can trigger other fixtures. • Fixtures scales from simple unit tests to complex functional test. • Fixtures can be reused across class, module or test session scope.

Slide 30

Slide 30 text

1 import pytest 2 3 def needs_bar_teardown(): 4 print('Inside "bar_teardown()"') 5 6 @pytest.fixture(scope='module') 7 def needs_bar(request): 8 print('Inside "needs bar()"') 9 request.addfinalizer(needs_bar_teardown) 10 11 def test_foo(needs_bar): 12 print('Inside "test_foo()"')

Slide 31

Slide 31 text

[hummer@localhost fixtures] $ py.test -sv fix.py ========================= test session starts ====================================== platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1 -- /usr/bin/python3 cachedir: .cache rootdir: /home/hummer/Study/Nov2015PythonPune/pyt/fixtures, inifile: collected 1 items fix.py::test_foo Inside "needs bar()" Inside "test_foo()" PASSEDInside "bar_teardown()" ========================= 1 passed in 0.00 seconds================================== [hummer@localhost fixtures] $

Slide 32

Slide 32 text

References • http://www.toptal.com/python/an-introduction- to-mocking-in-python • https://docs.python.org/dev/library/unittest. mock.html • http://pytest.org/latest/contents.html • http://pythontesting.net/