# pytest vs unittest: Which Should You Use?

[pytest](https://pydevtools.com/handbook/reference/pytest.md) and [unittest](https://docs.python.org/3/library/unittest.html) both run Python tests. unittest ships in the standard library and organizes tests as classes; pytest is a third-party package that lets tests be plain functions.

## Start with pytest

Use pytest for almost every new project. It requires less code per test, reports failures in more detail, and its fixture system and plugins hold up as a suite grows. The Python ecosystem has largely settled on it: Django, Flask, FastAPI, pandas, NumPy, and SQLAlchemy all test with pytest or ship first-class pytest support.

One reason genuinely calls for running tests with unittest instead of pytest: you cannot install third-party packages, so the standard library is all you have. Everything else people cite dissolves on inspection. An existing unittest suite runs under pytest unchanged, so "we already use unittest" is a reason to keep those tests, not to keep the runner. Even a preference for class-based tests is covered, because pytest groups tests in plain classes and runs `unittest.TestCase` subclasses, `setUp`/`tearDown` and all.

## How the two frameworks differ

### Writing assertions

unittest uses a class per group of tests and a named method for each check:

```python
import unittest

class TestAdd(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)
```

pytest uses a plain function and Python's built-in `assert`:

```python
def test_add():
    assert add(2, 3) == 5
```

The payoff shows up on failure. pytest rewrites `assert` statements to report the operands, so a broken check explains itself:

```console
$ pytest
>       assert add(2, 3) == 6
E       assert 5 == 6
```

unittest reports the same failure as `AssertionError: 5 != 6`. On two integers the difference is small. On a mismatched dict or a long list, pytest's introspection shows exactly which element diverged, while unittest sends you to the method's docstring or a custom message.

### Managing setup and teardown

unittest shares setup through `setUp` and `tearDown` methods that run before and after every test in a class:

```python
class TestApi(unittest.TestCase):
    def setUp(self):
        self.client = ApiClient()

    def tearDown(self):
        self.client.close()
```

pytest uses [fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html): functions a test requests by naming them as parameters. A fixture can be scoped to a function, class, module, or session, so expensive setup runs once and is shared:

```python
import pytest

@pytest.fixture
def client():
    c = ApiClient()
    yield c
    c.close()

def test_call(client):
    assert client.get().status_code == 200
```

Fixtures compose (one fixture can request another) and live in a shared `conftest.py` without inheritance. That composability is the feature that teams miss most after moving away from `setUp`/`tearDown`.

### Discovering and selecting tests

Both frameworks auto-discover tests, but pytest's selection is finer-grained. pytest collects files named `test_*.py`, functions named `test_*`, and classes named `Test*`, then lets you narrow a run by node id, substring, or [marker](https://pydevtools.com/handbook/reference/pytest.md):

```bash
pytest tests/test_api.py::test_login   # one test
pytest -k "login"                       # substring match
pytest -m slow                          # tests marked @pytest.mark.slow
```

unittest discovers `TestCase` subclasses and supports `python -m unittest discover`, but has no built-in equivalent of `-k` or markers for ad-hoc selection.

### Extending with plugins

pytest has a plugin ecosystem that unittest lacks. Parallel runs come from [pytest-xdist](https://pydevtools.com/handbook/how-to/how-to-run-tests-in-parallel-with-pytest-xdist.md), coverage from [pytest-cov](https://pydevtools.com/handbook/how-to/how-to-measure-code-coverage-with-pytest-cov.md), and dozens more cover snapshots and property-based testing. See [essential pytest plugins](https://pydevtools.com/handbook/explanation/essential-pytest-plugins.md) for the ones worth installing early. Parameterizing a test across inputs is built in:

```python
@pytest.mark.parametrize("n,expected", [(2, 4), (3, 9)])
def test_square(n, expected):
    assert n * n == expected
```

unittest covers the same ground with `subTest` and third-party runners, but the batteries are not included the way pytest's are.

## When to run with unittest instead

One situation genuinely calls for running tests with unittest rather than pytest: the runtime forbids third-party packages, so the standard library is all you have (a locked-down build, a security policy, a minimal container). That is the whole list.

Two things people expect to be on that list are not. A preference for class-based tests does not qualify, because pytest supports plain test classes and runs `unittest.TestCase` subclasses with `setUp`/`tearDown` intact. A legacy unittest suite does not qualify either, since pytest runs it unchanged. unittest is not deprecated and stays a stable, maintained choice under that one constraint; everywhere else, pytest's lower boilerplate pays off within the first dozen tests.

## You don't have to choose all at once

pytest runs `unittest.TestCase` subclasses with no changes. Point pytest at a unittest suite and it discovers and executes those classes alongside any plain-function tests:

```console
$ pytest -q
..                                    [100%]
2 passed in 0.01s
```

That one-way compatibility is the migration path: adopt pytest as the runner today, keep every existing unittest test green, and write new tests as plain functions. Convert old classes only when you touch them. The reverse does not hold, since `python -m unittest` ignores pytest's function-style tests, so once pytest is the runner there is little reason to go back.

To set up pytest from an empty project, follow [Setting up testing with pytest and uv](https://pydevtools.com/handbook/tutorial/setting-up-testing-with-pytest-and-uv.md).

## Learn More

- [pytest](https://pydevtools.com/handbook/reference/pytest.md) reference
- [Setting up testing with pytest and uv](https://pydevtools.com/handbook/tutorial/setting-up-testing-with-pytest-and-uv.md) (Tutorial)
- [How to run tests using uv](https://pydevtools.com/handbook/how-to/how-to-run-tests-using-uv.md)
- [How to parameterize tests with pytest](https://pydevtools.com/handbook/how-to/how-to-parameterize-tests-with-pytest.md)
- [Essential pytest plugins for reliable, fast test suites](https://pydevtools.com/handbook/explanation/essential-pytest-plugins.md)
- [unittest documentation](https://docs.python.org/3/library/unittest.html)
- [pytest documentation](https://docs.pytest.org/)
