Lesson 11 · Pytest deep dive
pytest Tutorial #11: Plugins — Extending pytest
On testing real Python code with pytest. pytest-mock, pytest-cov, and pytest-xdist: three real popular plugins. Let's get into it. A pytest plugin is a…
- CoursePytest deep dive
- Lesson11 of 18
- FormatJupyter notebook · 6 code cells
What you'll learn
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbpytest Deep-Dive, Video 11: Plugins#
- Video eleven of an eighteen-part series on testing real Python code with pytest.
- pytest-mock, pytest-cov, and pytest-xdist: three real popular plugins.
- Let's get into it.
Part 1: What a pytest Plugin Actually Is#
import pytest, tempfile, os
def run_pytest(code_str, args=None):
with tempfile.NamedTemporaryFile(mode='w', suffix='_test.py', delete=False) as f:
f.write(code_str)
path = f.name
result = pytest.main([path, *(args or [])])
os.unlink(path)
return result
Part 2: pytest-mock - the mocker Fixture#
code = '''
def send_email(sender, to, subject):
sender.send(to, subject)
def test_send_email(mocker):
fake_sender = mocker.Mock()
send_email(fake_sender, 'a@b.com', 'hi')
fake_sender.send.assert_called_once_with('a@b.com', 'hi')
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 3: mocker.patch - Automatic Cleanup#
code = '''
import random
def roll_die():
return random.randint(1, 6)
def test_patch_via_mocker(mocker):
mocker.patch('random.randint', return_value=4)
assert roll_die() == 4
def test_unpatched_after(mocker):
result = roll_die()
assert 1 <= result <= 6
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 4: pytest-cov - Measuring Coverage#
import tempfile, os, sys
tmpdir = tempfile.mkdtemp()
mymod_path = os.path.join(tmpdir, 'mymod.py')
with open(mymod_path, 'w') as f:
f.write('def add(a, b):\n return a + b\n\ndef unused_function():\n return "never called"\n')
test_path = os.path.join(tmpdir, 'test_mymod.py')
with open(test_path, 'w') as f:
f.write('from mymod import add\n\ndef test_add():\n assert add(2, 3) == 5\n')
sys.path.insert(0, tmpdir)
exit_code = pytest.main([test_path, '--cov=mymod', '--cov-report=term-missing', '-v', '--no-header'])
print(exit_code)
Part 5: pytest-xdist - Running Tests in Parallel#
code = '''
def test_a():
assert 1 + 1 == 2
def test_b():
assert 2 + 2 == 4
def test_c():
assert 3 + 3 == 6
def test_d():
assert 4 + 4 == 8
'''
exit_code = run_pytest(code, ['-n', '2', '-v', '--no-header'])
print(exit_code)
Part 6: A Real Pattern - mocker in an Ordinary Test Suite#
code = '''
class NotificationService:
def __init__(self, transport):
self.transport = transport
self.sent_count = 0
def notify(self, user_id, message):
self.transport.send(user_id, message)
self.sent_count += 1
def test_notify_calls_transport(mocker):
fake_transport = mocker.Mock()
service = NotificationService(fake_transport)
service.notify(42, 'hello')
fake_transport.send.assert_called_once_with(42, 'hello')
def test_notify_increments_count(mocker):
fake_transport = mocker.Mock()
service = NotificationService(fake_transport)
service.notify(1, 'a')
service.notify(2, 'b')
assert service.sent_count == 2
def test_sent_count_starts_at_zero(mocker):
service = NotificationService(mocker.Mock())
assert service.sent_count == 0
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Wrap-Up: What You Learned#
- A pytest plugin is a normal, pip-installable package that adds fixtures, markers, or CLI flags.
- pytest-mock provides the mocker fixture, wrapping unittest.mock with automatic cleanup after each test.
- mocker.patch works like unittest.mock.patch but needs no with block; it reverts automatically.
- pytest-cov, via --cov and --cov-report=term-missing, reports coverage percentage and uncovered line numbers.
- pytest-xdist, via -n, distributes tests across parallel worker processes to speed up a growing suite.
- Plugin-provided fixtures like mocker mix naturally into an ordinary, otherwise-unmocked test suite.
- That wraps up plugins. Next up: Coverage With pytest-cov.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



