Skip to content

Instantly share code, notes, and snippets.

@Sharadh
Created July 13, 2020 03:12
Show Gist options
  • Save Sharadh/bed3c9b9ae325e2db166e15d6d3ce960 to your computer and use it in GitHub Desktop.
Save Sharadh/bed3c9b9ae325e2db166e15d6d3ce960 to your computer and use it in GitHub Desktop.
Code snippets to accompany blogpost on 5 pytest best practices
"""Add this to <project-root>/mocker_over_mock.py"""
import pytest
try:
import mock # fails on Python 3
except ImportError:
from unittest import mock
def test_fn():
return 42
def another_test_fn():
return 42
class TestManualMocking(object):
"""This is dangerous because we could forget to call ``stop``,
or the test could error out; both would leak state across tests
"""
@pytest.mark.xfail(strict=True, msg="We want this test to fail.")
def test_manual(self):
patcher = mock.patch("mocker_over_mock.test_fn", return_value=84)
patcher.start()
assert test_fn() == 42
assert False
patcher.stop()
def test_manual_follow_up(self):
assert test_fn() == 42, "Looks like someone leaked state!"
class TestDecoratorMocking(object):
"""This is better, but:
1. Confusing when we start layering ``pytest`` decorators like
``@pytest.mark`` with ``@mock.patch``.
2. Doesn't work when used with fixtures.
3. Forces you to accept `mock_fn` as an argument even when the
mock is just set up and never used in your test - more boilerplate.
"""
@pytest.mark.xfail(strict=True, msg="We want this test to fail.")
@mock.patch("mocker_over_mock.another_test_fn", return_value=84)
def test_decorator(self, mock_fn):
assert another_test_fn() == 84
assert False
def test_decorator_follow_up(self):
assert another_test_fn() == 42
@pytest.fixture
@mock.patch("mocker_over_mock.another_test_fn", return_value=84)
def mock_fn(self, _):
return
def test_decorator_with_fixture(self, mock_fn):
assert another_test_fn() == 84, "@mock and fixtures don't mix!"
class TestMockerFixture(object):
"""This is best; the mocker fixture reduces boilerplate and
stays out of the declarative pytest syntax.
"""
@pytest.mark.xfail(strict=True, msg="We want this test to fail.")
def test_mocker(self, mocker):
mocker.patch("mocker_over_mock.another_test_fn", return_value=84)
assert another_test_fn() == 84
assert False
def test_mocker_follow_up(self):
assert another_test_fn() == 42
@pytest.fixture
def mock_fn(self, mocker):
return mocker.patch("mocker_over_mock.test_basic.another_test_fn", return_value=84)
def test_mocker_with_fixture(self, mock_fn):
assert another_test_fn() == 84
from copy import deepcopy
import pytest
@pytest.fixture
def alex():
return {
"name": "Alex",
"team": "Green",
}
@pytest.fixture
def bala(alex):
alex["name"] = "Bala"
return alex
@pytest.fixture
def carlos(alex):
_carlos = deepcopy(alex)
_carlos["name"] = "Carlos"
return _carlos
def test_antipattern(alex, bala):
assert alex == {"name": "Alex", "team": "Green"}
assert bala == {"name": "Bala", "team": "Green"}
def test_pattern(alex, carlos):
assert alex == {"name": "Alex", "team": "Green"}
assert carlos == {"name": "Carlos", "team": "Green"}
import pytest
def divide(a, b):
return a / b
@pytest.mark.parametrize("a, b, expected, is_error", [
(1, 1, 1, False),
(42, 1, 42, False),
(84, 2, 42, False),
(42, "b", TypeError, True),
("a", 42, TypeError, True),
(42, 0, ZeroDivisionError, True),
])
def test_divide_antipattern(a, b, expected, is_error):
if is_error:
with pytest.raises(expected):
divide(a, b)
else:
assert divide(a, b) == expected
@pytest.mark.parametrize("a, b, expected", [
(1, 1, 1),
(42, 1, 42),
(84, 2, 42),
])
def test_divide_ok(a, b, expected):
assert divide(a, b) == expected
@pytest.mark.parametrize("a, b, expected", [
(42, "b", TypeError),
("a", 42, TypeError),
(42, 0, ZeroDivisionError),
])
def test_divide_error(a, b, expected):
with pytest.raises(expected):
divide(a, b)
import pytest
def process_file(fp):
"""Toy function that returns an array of line lengths."""
return [len(l.strip()) for l in fp.readlines()]
@pytest.mark.parametrize("filename, expected", [
("first.txt", [3, 3, 3]),
("second.txt", [5, 5]),
])
def test_antipattern(filename, expected):
with open("resources/" + filename) as fp:
assert process_file(fp) == expected
@pytest.mark.parametrize("contents, expected", [
("foo\nbar\nbaz", [3, 3, 3]),
("hello\nworld", [5, 5]),
])
def test_pattern(tmpdir, contents, expected):
tmp_file = tmpdir.join("testfile.txt")
tmp_file.write(contents)
with tmp_file.open() as fp:
assert process_file(fp) == expected
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment