•6 min read

A Practical pytest Tutorial That Actually Fixes Early Gotchas

A Practical pytest Tutorial That Actually Fixes Early Gotchas
pytest

pytest fixture

Click to reveal
pytest
A function that provides shared setup/teardown for tests. Defined with @pytest.fixture and injected into test functions by name. Runs once per test/session/module/function scope.

pytest fixture

pytest

pytest parametrize

Click to reveal
pytest
Run the same test with multiple inputs: @pytest.mark.parametrize('a,b,expected', [(1,2,3), (-1,1,0)]). Creates separate test instances for each case.

pytest parametrize

pytest

pytest.raises

Click to reveal
pytest
Context manager to assert an exception is raised: with pytest.raises(ZeroDivisionError): divide(1,0). Avoids try/except in tests.

pytest.raises

pytest

test discovery

Click to reveal
pytest
pytest auto-finds test_*.py files and test_* functions. Run specific tests with: pytest test_math.py::test_addition

test discovery

Practical Pytest: Stop Guessing, Start Testing

I spent way too long wondering why my tests were "passing" when my code was definitely broken. Pytest is incredibly powerful, but it quietly lets you get away with bad habits early on.

Audio Briefing
0:00 / 0:00
The Golden Rule

Tests should fail when the code is broken. If you write a test and you haven't seen it fail on purpose at least once, don't trust it.


1. The Core Workflow

Install and Run

Install with pip install pytest. Run it by typing pytest in your project root. It will automatically find files named test_*.py.

Write a Basic Test

Pytest doesn't require a heavy DSL. Just write functions starting with test_ and use plain Python assert statements.

def test_addition():
    assert 1 + 1 == 2

Target Specific Tests

Speed up your feedback loop by running only the specific test you are working on: pytest test_math.py::test_addition


Advertisement

2. Powerful Pytest Features

Fixtures: Shared Setup

Use fixtures to set up database connections or dummy data once, instead of copy-pasting it into every test.

@pytest.fixture
def empty_cart():
    return ShoppingCart()

def test_add_item(empty_cart):
    empty_cart.add("apple")
    assert len(empty_cart) == 1
Fixture ScopeWhen it RunsUse Case
function (default)
Before each test function
Isolated test data
class
Once per test class
Shared setup for class tests
module
Once per test file
Expensive DB connections
session
Once per test session
Global config, expensive fixtures
Parametrize: Test Multiple Cases

Stop writing test_add_positive, test_add_negative. Use parametrization to run the same logic with different inputs.

@pytest.mark.parametrize("a,b,expected", [(1,2,3), (-1,1,0)])
def test_add(a, b, expected):
    assert add(a, b) == expected
Testing Exceptions

Don't use try/except to test for errors. Use pytest.raises to guarantee an exception is thrown.

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        divide(1, 0)

3. Common Patterns

Use tmp_path fixture (built-in) for isolated test files:

def test_write_read(tmp_path):
    file = tmp_path / "data.txt"
    file.write_text("hello")
    assert file.read_text() == "hello"

Built-in fixture for patching without external library:

def test_api_call(monkeypatch):
    monkeypatch.setattr("myapp.api.get", lambda: {"status": "ok"})
    result = myapp.api.get()
    assert result["status"] == "ok"

Mark tests and run subsets:

# In pytest.ini or pyproject.toml
markers = [
    "slow: marks tests as slow",
    "integration: marks tests as integration tests"
]

# In test file
@pytest.mark.slow
def test_heavy_computation():
    ...

# Run only slow tests
pytest -m slow
# Exclude slow tests
pytest -m "not slow"

Decision Reference

GoalUseExample
Shared test data
<code>@pytest.fixture</code>
DB connections, config objects
Multiple inputs, same logic
<code>@pytest.mark.parametrize</code>
Edge cases, boundary values
Assert exception raised
<code>pytest.raises(Error)</code>
Division by zero, invalid input
Isolate filesystem operations
<code>tmp_path</code> fixture
File read/write tests
Patch external dependency
<code>monkeypatch</code> fixture
API calls, environment vars
Run subset of tests
<code>@pytest.mark.slow</code> + <code>pytest -m slow</code>
CI separation, dev feedback
pytest Testing Python
Advertisement

You Might Also Like

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement