LearnThatStack Ace your next interview
Testing · Free

Pytest.
Interview cheat sheet.

Quick reference for Pytest - sectioned for fast scanning. Skim the part you're shaky on, walk in confident.

Testing 17-section reference ~6 min read

Summary

Pytest is Python's most powerful testing framework, featuring simple syntax, powerful fixtures, and extensive plugin ecosystem. Master fixtures, parametrization, markers, mocking with monkeypatch, and configuration to write clean, maintainable test suites that leverage pytest's automatic test discovery and detailed assertion introspection.

1. Installation & Basic Setup

pip install pytest
pip install pytest-cov  # For coverage
pip install pytest-mock  # For mocking

2. Test Discovery & Naming Conventions

Pytest automatically discovers tests following these patterns:

  • Test files: test_*.py or *_test.py
  • Test classes: Test* (without __init__ method)
  • Test functions: test_*
# test_example.py
def test_addition():
    assert 1 + 1 == 2

class TestMath:
    def test_multiplication(self):
        assert 2 * 3 == 6

3. Running Tests

pytest                    # Run all tests
pytest test_file.py      # Run specific file
pytest test_file.py::test_func  # Run specific test
pytest -v                # Verbose output
pytest -s                # Show print statements
pytest -k "keyword"      # Run tests matching keyword
pytest -x                # Stop on first failure
pytest --maxfail=2       # Stop after 2 failures

4. Assertions

# Basic assertions
assert value == expected
assert value != expected
assert value > threshold
assert value in collection
assert value is None
assert value is not None

# Exception testing
import pytest

def test_exception():
    with pytest.raises(ValueError):
        raise ValueError("Error message")
    
    # Check exception message
    with pytest.raises(ValueError, match="Error.*"):
        raise ValueError("Error message")

# Approximate assertions
assert pytest.approx(0.1 + 0.2) == 0.3
assert [0.1, 0.2] == pytest.approx([0.1, 0.2])

5. Fixtures

Basic Fixtures

import pytest

@pytest.fixture
def sample_data():
    return {"name": "John", "age": 30}

def test_with_fixture(sample_data):
    assert sample_data["name"] == "John"

# Fixture with teardown
@pytest.fixture
def database():
    # Setup
    db = Database()
    db.connect()
    yield db  # This is where the test runs
    # Teardown
    db.disconnect()

Fixture Scopes

@pytest.fixture(scope="function")  # Default - per test
def func_fixture():
    return "data"

@pytest.fixture(scope="class")     # Per test class
def class_fixture():
    return "shared_class_data"

@pytest.fixture(scope="module")    # Per module
def module_fixture():
    return "shared_module_data"

@pytest.fixture(scope="session")   # Once per session
def session_fixture():
    return "shared_session_data"

Built-in Fixtures

def test_tmp_path(tmp_path):
    # tmp_path is a pathlib.Path object
    file = tmp_path / "test.txt"
    file.write_text("content")
    assert file.read_text() == "content"

def test_capsys(capsys):
    print("Hello")
    captured = capsys.readouterr()
    assert captured.out == "Hello\n"

def test_monkeypatch(monkeypatch):
    monkeypatch.setenv("USER", "testuser")
    assert os.environ["USER"] == "testuser"

6. Parametrization

# Single parameter
@pytest.mark.parametrize("input,expected", [
    (2, 4),
    (3, 9),
    (4, 16)
])
def test_square(input, expected):
    assert input ** 2 == expected

# Multiple parameters
@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (5, 5, 10),
    (-1, 1, 0)
])
def test_add(a, b, expected):
    assert a + b == expected

# Parameter IDs for better test names
@pytest.mark.parametrize("value", [1, 2, 3], ids=["one", "two", "three"])
def test_values(value):
    assert value > 0

7. Markers

# Built-in markers
@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
    pass

@pytest.mark.skipif(sys.version_info < (3, 8), reason="Requires Python 3.8+")
def test_new_feature():
    pass

@pytest.mark.xfail(reason="Known bug")
def test_broken_feature():
    assert False

# Custom markers (define in pytest.ini)
@pytest.mark.slow
def test_heavy_computation():
    pass

@pytest.mark.integration
def test_api_call():
    pass

# Run specific markers
# pytest -m slow
# pytest -m "not slow"

8. Mocking & Monkeypatching

Using unittest.mock

from unittest.mock import Mock, patch

def test_with_mock():
    mock = Mock(return_value=42)
    assert mock() == 42
    mock.assert_called_once()

# Patching
@patch('module.function')
def test_with_patch(mock_func):
    mock_func.return_value = "mocked"
    result = module.function()
    assert result == "mocked"

# Context manager
def test_patch_context():
    with patch('os.path.exists') as mock_exists:
        mock_exists.return_value = True
        assert os.path.exists('/fake/path')

Monkeypatch

def test_monkeypatch_function(monkeypatch):
    def mock_func():
        return "mocked"
    
    monkeypatch.setattr("module.function", mock_func)
    assert module.function() == "mocked"

def test_monkeypatch_env(monkeypatch):
    monkeypatch.setenv("API_KEY", "test_key")
    assert os.environ["API_KEY"] == "test_key"

9. Configuration Files

pytest.ini

[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --strict-markers
markers =
    slow: marks tests as slow
    integration: marks tests as integration tests

conftest.py

# conftest.py - Shared fixtures across tests
import pytest

@pytest.fixture(autouse=True)
def reset_database():
    # Runs before each test automatically
    yield
    # Cleanup after each test

@pytest.fixture
def client():
    return TestClient()

# Hooks
def pytest_collection_modifyitems(items):
    """Modify test collection"""
    for item in items:
        # Add markers based on test names
        if "slow" in item.nodeid:
            item.add_marker(pytest.mark.slow)

10. Advanced Patterns

Testing Classes

class TestUserService:
    @pytest.fixture(autouse=True)
    def setup(self):
        self.service = UserService()
    
    def test_create_user(self):
        user = self.service.create("John")
        assert user.name == "John"
    
    def test_delete_user(self):
        user = self.service.create("Jane")
        self.service.delete(user.id)
        assert self.service.get(user.id) is None

Async Testing

import pytest
import asyncio

@pytest.mark.asyncio
async def test_async_function():
    result = await async_function()
    assert result == expected

# Install: pip install pytest-asyncio

Test Doubles Pattern

class FakeRepository:
    def __init__(self):
        self.data = {}
    
    def save(self, id, value):
        self.data[id] = value
    
    def get(self, id):
        return self.data.get(id)

def test_service_with_fake():
    fake_repo = FakeRepository()
    service = Service(fake_repo)
    service.process("data")
    assert fake_repo.get(1) == "processed_data"

11. Coverage

# Run with coverage
pytest --cov=myproject tests/
pytest --cov=myproject --cov-report=html
pytest --cov=myproject --cov-report=term-missing

# Coverage configuration in .coveragerc
[run]
source = myproject
omit = */tests/*,*/venv/*

[report]
exclude_lines =
    pragma: no cover
    def __repr__
    raise AssertionError
    raise NotImplementedError

12. Best Practices & Interview Tips

Test Organization

# Arrange-Act-Assert pattern
def test_user_creation():
    # Arrange
    user_data = {"name": "John", "email": "john@example.com"}
    
    # Act
    user = create_user(user_data)
    
    # Assert
    assert user.name == "John"
    assert user.email == "john@example.com"

Critical Interview Topics

Test Isolation Principles

# Bad
class TestCounter:
    counter = 0
    
    def test_increment(self):
        self.counter += 1
        assert self.counter == 1  # Fails if run after other tests

# Good
class TestCounter:
    def test_increment(self):
        counter = Counter()
        counter.increment()
        assert counter.value == 1
  1. Testing Edge Cases
@pytest.mark.parametrize("input,expected", [
    ([], 0),              # Empty list
    ([1], 1),             # Single element
    ([1, 2, 3], 6),       # Normal case
    ([-1, -2, -3], -6),   # Negative numbers
    ([1.5, 2.5], 4.0),    # Floats
])
def test_sum_function(input, expected):
    assert sum(input) == expected
  1. Testing Exceptions
def test_division_by_zero():
    with pytest.raises(ZeroDivisionError) as exc_info:
        1 / 0
    assert "division by zero" in str(exc_info.value)
  1. Mocking External Dependencies
def test_api_call(mocker):
    # Using pytest-mock
    mock_response = mocker.Mock()
    mock_response.json.return_value = {"status": "success"}
    mocker.patch("requests.get", return_value=mock_response)
    
    result = make_api_call()
    assert result["status"] == "success"

13. Quick Reference Commands

# Discovery
pytest --collect-only     # Show what tests would run

# Output
pytest -v                 # Verbose
pytest -q                 # Quiet
pytest --tb=short        # Short traceback
pytest --tb=no           # No traceback

# Selection
pytest -k "test_method"  # Keyword expression
pytest -m "not slow"     # Marker expression

# Debugging
pytest --pdb             # Drop to debugger on failures
pytest --lf              # Run last failed
pytest --ff              # Run failed first

# Parallel execution (requires pytest-xdist)
pytest -n 4              # Run on 4 CPUs

14. Common Gotchas

  1. Mutable Default Arguments in Fixtures
# Bad
@pytest.fixture
def data():
    return []  # Same list for all tests!

# Good
@pytest.fixture
def data():
    return []  # New list for each test
  1. Import Errors
# Ensure test files can import your module
# Add __init__.py to test directories
# Or use PYTHONPATH: export PYTHONPATH=.
  1. Fixture Dependency
@pytest.fixture
def user(database):  # Depends on database fixture
    return database.create_user("test")

def test_user_exists(user, database):
    assert database.get_user(user.id) is not None

Interview Success Strategies

Pytest vs Unittest Comparison

  • Pytest: Simple assert statements, powerful fixtures, better output
  • Unittest: Class-based, more verbose, standard library
  • Migration: Pytest can run unittest tests

Fixture Expertise

  • Scopes: function (default), class, module, session
  • Autouse: Automatic fixture application
  • Yield Fixtures: Setup and teardown in one function
  • Fixture Dependencies: Fixtures can depend on other fixtures

Mocking Strategies

  • Monkeypatch: Built-in pytest fixture for patching
  • Mock/patch: unittest.mock integration
  • pytest-mock: Enhanced mocking plugin
  • Fake Objects: Test doubles for complex dependencies

Advanced Testing Patterns

  • Async Testing: pytest-asyncio for async/await
  • Parallel Execution: pytest-xdist for speed
  • Property Testing: hypothesis integration
  • Benchmark Testing: pytest-benchmark for performance

Best Practices to Demonstrate

  • AAA Pattern: Arrange-Act-Assert structure
  • Descriptive Names: test_should_return_error_when_invalid_input
  • Parametrization: Cover edge cases efficiently
  • Markers: Organize tests with custom markers
  • Coverage Goals: Aim for 80%+ with meaningful tests

Key Points to Remember

  • Pytest's assert introspection provides detailed failure messages
  • Fixtures promote reusability and reduce boilerplate
  • Parametrization eliminates test duplication
  • conftest.py shares fixtures across test modules
  • pytest.ini configures project-wide settings
Found this useful? Pass it on.
Pro · $10/mo

The sheet is free. Pro goes deeper.

Pro opens the full question library behind every sheet, every refresher and a monthly AI allowance. One subscription, all formats.

Full question library All refreshers Cancel anytime