pytest Essentials Interview Questions & Answers
6 questions Updated 2026-06-18
Python interview questions on pytest — pytest vs unittest, fixtures and scope, parametrize, mocking with monkeypatch and unittest.mock, pytest.raises, and conftest.py.
unittest is the standard-library framework, modeled on xUnit: tests
are methods on a TestCase subclass and you use self.assertEqual,
self.assertTrue, etc. pytest is a third-party framework that runs
plain functions using the bare assert statement, with rich failure
introspection, fixtures, and parametrize.
# unittest
import unittest
class TestMath(unittest.TestCase):
def test_add(self):
self.assertEqual(1 + 1, 2)
# pytest — just a function and assert
def test_add():
assert 1 + 1 == 2
pytest can also run existing unittest tests, so adopting it is low-risk. Rule of thumb: prefer pytest for new code — less boilerplate and better output — while unittest is fine when you must avoid dependencies.
A fixture is a function decorated with @pytest.fixture that provides
setup (and teardown) for tests. A test requests it simply by naming it as
a parameter, and pytest injects the return value. Using yield lets code
after the yield run as teardown. The scope controls how often the
fixture is created: function (default), class, module, or session.
import pytest
@pytest.fixture(scope="module") # created once per module
def db():
conn = connect()
yield conn # value handed to tests
conn.close() # teardown after tests finish
def test_query(db): # db injected by name
assert db.ping()
Wider scopes share expensive resources (DB connections, servers) across many
tests for speed; narrower scopes give better isolation. Rule of thumb:
default to function scope and only widen when setup is costly.
@pytest.mark.parametrize runs the same test function multiple times with
different arguments, generating one separate test case per row. Each case
passes or fails independently, so a single bad input doesn't hide the others —
far cleaner than a loop inside one test.
import pytest
@pytest.mark.parametrize("value, expected", [
(2, 4),
(3, 9),
(4, 16),
])
def test_square(value, expected):
assert value ** 2 == expected
pytest reports each as test_square[2-4], test_square[3-9], etc., and you
can stack decorators to get the cross product of inputs. Rule of thumb:
use parametrize for the same logic across many inputs instead of copy-pasted
tests or in-test loops.
The built-in monkeypatch fixture replaces attributes, dict items, or
env vars for the duration of a test and auto-restores them afterward —
ideal for swapping out a function or setting os.environ. unittest.mock
(Mock, patch) creates mock objects that record calls and let you set
return values or side effects — best when you need to assert how a
dependency was called.
import requests, mymodule
def test_with_monkeypatch(monkeypatch):
monkeypatch.setattr(requests, "get", lambda url: {"ok": True})
assert mymodule.fetch() == {"ok": True}
from unittest.mock import patch
def test_with_mock():
with patch("mymodule.requests.get") as mock_get:
mock_get.return_value = {"ok": True}
mymodule.fetch()
mock_get.assert_called_once() # verify the interaction
A key gotcha: patch where the name is looked up (mymodule.requests.get),
not where it's defined. Rule of thumb: monkeypatch for simple replacements,
unittest.mock when you need to inspect calls.
Use the pytest.raises context manager: the test passes only if the
expected exception is raised inside the with block, and fails if no
exception (or the wrong one) occurs. You can capture the exception via
as excinfo to assert on its message, and use match= for a regex check.
import pytest
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
1 / 0
def test_message():
with pytest.raises(ValueError, match="invalid"):
int("not a number") # ValueError: invalid literal...
# or: assert "invalid" in str(excinfo.value)
It cleanly replaces a try/except/pytest.fail dance. Rule of thumb: always
assert on the specific exception type (and ideally the message) so the
test can't pass for the wrong reason.
conftest.py is a special pytest file for shared fixtures and hooks.
pytest auto-discovers it — no import needed — and any fixture defined
there is available to every test in that directory and its subdirectories.
It's the standard place to put fixtures used across multiple test files.
# tests/conftest.py
import pytest
@pytest.fixture
def client():
return create_test_client()
# tests/test_users.py — no import required
def test_login(client): # 'client' resolved from conftest
assert client.login("ada")
You can also register plugins, define hooks like pytest_addoption, and place
a conftest.py at multiple levels for scoped sharing. Rule of thumb: put
a fixture in conftest.py once more than one test file needs it, instead of
importing it around.
Practice tests are coming soon
Get notified when interactive mock interviews and quizzes launch.