Cheuk Ting Ho
Developer advocate / Data Scientist - support open-source and building the community.
Online Absolute Beginner Python Tutorials
Every Sunday 2pm (UK time/ BST)
by Cheuk Ting Ho
Get this slide deck: https://slides.com/cheukting_ho/python-pytest-mock
Python objects - int, float, str, list, dict, bool
Control flows - if-else, for loop, while loop
Functions, modeuls, classes and decorators
strings operations and regex with re
pytest basics
Fixtures are functions, which will run before each test function to which it is applied. Fixtures are used to feed some data to the tests such as database connections, URLs to test and some sort of input data - https://www.tutorialspoint.com/pytest/pytest_fixtures.htm
import pytest
@pytest.fixture
def smtp():
import smtplib
return smtplib.SMTP("smtp.gmail.com")
def test_ehlo(smtp):
response, msg = smtp.ehlo()
assert response == 250
If you know a test will fail for now (and it's 3am) you just wanna mark it fail so you can fix it later. Or if you wanna skip a test temporary... what should you do?
Pytest will execute the xfailed test, but it will not be considered as part failed or passed tests. Details of these tests will not be printed even if the test fails (remember pytest usually prints the failed test details). We can xfail tests using the following marker −
@pytest.mark.xfail
(you can also mark the reason for failing)
Skipping a test means that the test will not be executed. We can skip tests using the following marker −
@pytest.mark.skip
https://www.tutorialspoint.com/pytest/pytest_xfail_skip_tests.htm
https://docs.pytest.org/en/2.8.7/skipping.html#marking-a-test-function-to-be-skipped
import sys
@pytest.mark.skipif(sys.version_info < (3,3),
reason="requires python3.3")
def test_function():
...
option 1: using fixture
import pytest
import smtplib
@pytest.fixture(scope="module",
params=["smtp.gmail.com", "mail.python.org"])
def smtp(request):
smtp = smtplib.SMTP(request.param)
def fin():
print ("finalizing %s" % smtp)
smtp.close()
request.addfinalizer(fin)
return smtp
option 2: @pytest.mark.parametrize
import pytest
from datetime import datetime, timedelta
testdata = [
(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]
@pytest.mark.parametrize("a,b,expected", testdata)
def test_timedistance_v0(a, b, expected):
diff = a - b
assert diff == expected
@pytest.mark.parametrize("a,b,expected", testdata, ids=["forward", "backward"])
def test_timedistance_v1(a, b, expected):
diff = a - b
assert diff == expected
It is possible to apply markers like skip and xfail to individual test instances when using parametrize:
import pytest
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.mark.xfail((1, 0)),
pytest.mark.xfail(reason="some bug")((1, 3)),
(2, 3),
(3, 4),
(4, 5),
pytest.mark.skipif("sys.version_info >= (3,0)")((10, 11)),
])
def test_increment(n, expected):
assert n + 1 == expected
Monkey patching is replacing a function/method/class by another at runtime, for testing purpses, fixing a bug or otherwise changing behaviour.
It is useful when your test involve some external funtions (e.g. getting a result from the backend with your API call)
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, raising=True)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
from pathlib import Path
def getssh():
"""Simple function to return expanded homedir ssh path."""
return Path.home() / ".ssh"
def test_getssh(monkeypatch):
# mocked return function to replace Path.home
# always return '/abc'
def mockreturn():
return Path("/abc")
# Application of the monkeypatch to replace Path.home
# with the behavior of mockreturn defined above.
monkeypatch.setattr(Path, "home", mockreturn)
# Calling getssh() will use mockreturn in place of Path.home
# for this test with the monkeypatch.
x = getssh()
assert x == Path("/abc/.ssh")
Note that in the example, you have to write your own mockreturn function. With a more complex object (requests library objs), it could be quite complicated to create a mock object yourself.
The mock library gives you an object you can use to monkey-patch. The mock object from the mock library also gives you excellent features out of the box that helps you test that the mock behaves a certain way.
https://docs.python.org/3/library/unittest.mock.html#module-unittest.mock
patch(), patch.object() and patch.dict()
https://docs.python.org/3/library/unittest.mock-examples.html#patch-decorators
from unittest import mock
import requests
from requests.exceptions import HTTPError
@mock.patch('requests.get')
def test_google_query(self, mock_get):
"""test google query method"""
mock_resp = self._mock_response(content="ELEPHANTS")
mock_get.return_value = mock_resp
result = google_query('elephants')
self.assertEqual(result, 'ELEPHANTS')
self.assertTrue(mock_resp.raise_for_status.called)
Note: With patch() it matters that you patch objects in the namespace where they are looked up. This is normally straightforward, but for a quick guide read where to patch.
https://docs.python.org/3/library/unittest.mock-examples.html#patch-decorators
Sunday 2pm (UK time/ BST)
There are also Mid Meet Py every Wednesday 1pm
Testing month in June
By Cheuk Ting Ho
Developer advocate / Data Scientist - support open-source and building the community.