Getting Started with Pytest

An introduction to pytest, the most popular testing framework for Python. Learn how its simple syntax, powerful fixture model, and rich plugin ecosystem can make writing tests easier and more enjoyable.

Testing is a critical part of writing robust and maintainable software. While Python has a built-in testing library, unittest, the community has largely embraced a third-party framework that makes testing simpler, more scalable, and more enjoyable: pytest.

Pytest is the de facto standard for testing in Python. Its simple syntax, powerful features, and rich plugin ecosystem have made it the go-to choice for projects of all sizes.

Why Pytest?

  • Simple Syntax: Pytest allows you to write tests as simple functions, using the standard Python assert statement. This is much less boilerplate than the class-based approach of unittest.
  • Powerful Fixtures: Pytest has a powerful and flexible fixture model that allows you to manage the setup and teardown of your test resources in a clean and reusable way.
  • Rich Plugin Ecosystem: There are hundreds of plugins available for pytest that extend its functionality, from integration with web frameworks like Django and Flask to tools for code coverage and parallel test execution.
  • Excellent Test Discovery: Pytest can automatically discover and run your tests without any explicit configuration, as long as you follow some simple naming conventions.

Your First Pytest Test

Let's say we have a simple function we want to test:

# my_app/functions.py
def add(a, b):
    return a + b

To write a test for this with pytest, you simply create a test file (e.g., tests/test_functions.py) and write a test function that starts with test_.

# tests/test_functions.py
from my_app.functions import add

def test_add():
    # Use a simple assert statement to check the result
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

That's it! To run your tests, you just run the pytest command in your terminal from the root of your project. Pytest will automatically find and run your test function.

Test Organization

Pytest's test discovery is based on naming conventions:

  • Files must be named test_*.py or *_test.py.
  • Test functions must be named test_*.
  • Test classes must be named Test* (and they cannot have an __init__ method).

Parametrizing Tests

Often, you want to run the same test logic with different inputs. Pytest makes this easy with the @pytest.mark.parametrize decorator.

import pytest
from my_app.functions import add

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

Pytest will run this test function four times, once for each tuple in the list, passing the values as arguments.

Managing State with Fixtures

Fixtures are the heart of pytest. A fixture is a function that provides a resource to your tests, such as a database connection, a temporary file, or an instance of a class. You declare a fixture with the @pytest.fixture decorator.

Tests can then receive fixtures by declaring them as arguments.

import pytest

class BankAccount:
    def __init__(self, starting_balance=0):
        self.balance = starting_balance

    def withdraw(self, amount):
        self.balance -= amount

# This is a fixture
@pytest.fixture
def bank_account():
    """Returns a BankAccount instance with a balance of 100."""
    return BankAccount(100)

# This test uses the fixture
def test_withdraw(bank_account):
    bank_account.withdraw(50)
    assert bank_account.balance == 50

Pytest will automatically call the bank_account fixture and pass its return value to the test_withdraw function. This is a powerful pattern for setting up the state your tests need in a clean, reusable, and explicit way.

Conclusion

Pytest is a powerful and elegant framework that takes a lot of the pain out of writing tests in Python. Its simple assertion syntax, powerful fixture model, and rich plugin ecosystem allow you to write tests that are clean, readable, and scalable. If you are writing tests in Python, you should be using pytest.