KEMBAR78
Python testing using mock and pytest | PDF
Python
Testing
using Mock
& PyTest
unittest.mock
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.
• 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.
Mock - Basics
>>> from unittest.mock import Mock
>>> m = Mock()
>>> m
<Mock id='140457934010912'>
>>> m.some_value = 23
>>> m.some_value
23
>>> m.other_value
<Mock name='mock.other_value' id='140457789462008'>
Mock Objects - Basics
>>> m.get_value(value=42)
<Mock name='mock.get_value()' id='140457789504816'>
>>> 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)
• 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
>>> 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
Using spec to define attr
>>> user_info = ['first_name', 'last_name', 'email']
>>> m = Mock(spec=user_info)
>>> m.first_name
<Mock name='mock.first_name' id='140032117032552'>
>>> m.address
raise AttributeError("Mock object has no attribute %
r" % name)
AttributeError: Mock object has no attribute 'address'
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
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.
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'))
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)
patching methods #1
>>> @patch('requests.Response')
... @patch('requests.Session')
... def test(session, response):
... assert session is requests.Session
... assert response is requests.Response
...
>>> test()
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')
>>>
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
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.
#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!!
>>> 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
>>> m = Mock()
>>> side_effect = lambda value: value ** 3
>>> m.side_effect = side_effect
>>> m(2)
8
#3 side_effect as function
Installation
For Python3
$ pip3 install -U pytest
For Python2
$ pip install -U pytest
or
$ easy_install -U pytest
What is pytest?
● A fully featured Python Testing tool.
● Do automated tests.
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
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
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========================================
$ 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
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.
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()"')
[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] $
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/

Python testing using mock and pytest

  • 1.
  • 2.
  • 3.
    unittest.mock Defn: unittest.mock isa 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.
  • 4.
    • As adeveloper 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.
  • 5.
  • 6.
    >>> from unittest.mockimport Mock >>> m = Mock() >>> m <Mock id='140457934010912'> >>> m.some_value = 23 >>> m.some_value 23 >>> m.other_value <Mock name='mock.other_value' id='140457789462008'> Mock Objects - Basics
  • 7.
    >>> m.get_value(value=42) <Mock name='mock.get_value()'id='140457789504816'> >>> 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)
  • 8.
    • Flexible objectsthat 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
  • 9.
    >>> from unittest.mockimport 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
  • 10.
    Using spec todefine attr >>> user_info = ['first_name', 'last_name', 'email'] >>> m = Mock(spec=user_info) >>> m.first_name <Mock name='mock.first_name' id='140032117032552'> >>> m.address raise AttributeError("Mock object has no attribute % r" % name) AttributeError: Mock object has no attribute 'address'
  • 11.
    Automatically create allspecs >>> 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
  • 12.
    Using Mock throughpatch • 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.
  • 13.
    1 # main.py 2import 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'))
  • 14.
    1 # tests.py 2import 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)
  • 15.
    patching methods #1 >>>@patch('requests.Response') ... @patch('requests.Session') ... def test(session, response): ... assert session is requests.Session ... assert response is requests.Response ... >>> test()
  • 16.
    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') >>>
  • 17.
    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
  • 18.
    Mock side_effect • Thiscan 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.
  • 19.
    #1 side_effect forException >>> m = Mock() >>> m.side_effect = ValueError('You are always gonna get this!!') >>> m() raise effect ValueError: You are always gonna get this!!
  • 20.
    >>> 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
  • 21.
    >>> m =Mock() >>> side_effect = lambda value: value ** 3 >>> m.side_effect = side_effect >>> m(2) 8 #3 side_effect as function
  • 23.
    Installation For Python3 $ pip3install -U pytest For Python2 $ pip install -U pytest or $ easy_install -U pytest
  • 24.
    What is pytest? ●A fully featured Python Testing tool. ● Do automated tests.
  • 25.
    Tests with lessBoilerplate 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
  • 26.
    1 def cube(number): 2return 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
  • 27.
    Running Tests pytest willrun 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========================================
  • 28.
    $ py.test Run entiretest 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
  • 29.
    pytest fixtures • Fixturesare 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.
  • 30.
    1 import pytest 2 3def 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()"')
  • 31.
    [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] $
  • 32.