pytest
unit testing
test automation
setup and teardown
Python testing

How do I correctly setup and teardown for my pytest class with tests?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In pytest, class-based setup and teardown works, but fixtures are usually the cleaner tool. The right choice depends on whether you need per-test state, per-class shared resources, or explicit old-style hooks for a small test class.

Prefer Fixtures for Most Setup Logic

Pytest fixtures are the normal way to prepare resources and clean them up. A fixture can return data to each test and use yield so teardown happens automatically afterward.

Here is a function-scoped example that runs once per test:

python
1import pytest
2
3
4@pytest.fixture
5def temp_user():
6    user = {"name": "alice", "active": True}
7    yield user
8    user.clear()
9
10
11class TestUserStatus:
12    def test_user_is_active(self, temp_user):
13        assert temp_user["active"] is True
14
15    def test_user_has_name(self, temp_user):
16        assert temp_user["name"] == "alice"

Each test gets a fresh fixture value, which keeps tests isolated. That isolation is one of the biggest reasons fixtures are preferred over mutable shared class state.

Use Class-Scoped Fixtures for Shared Expensive Resources

If setup is expensive and can be shared safely across tests in one class, use a fixture with scope="class".

python
1import pytest
2
3
4@pytest.fixture(scope="class")
5def db_connection(request):
6    connection = {"connected": True}
7    request.cls.connection = connection
8    yield
9    connection["connected"] = False
10
11
12@pytest.mark.usefixtures("db_connection")
13class TestDatabaseQueries:
14    def test_connection_exists(self):
15        assert self.connection["connected"] is True
16
17    def test_reuse_connection(self):
18        assert "connected" in self.connection

This runs the setup once for the class and tears it down once after the final test in that class. It is a good fit for temporary databases, service clients, or other shared but controlled resources.

xUnit-Style Hooks Still Work

Pytest also supports classic hooks such as setup_method, teardown_method, setup_class, and teardown_class.

python
1class TestCounter:
2    @classmethod
3    def setup_class(cls):
4        cls.shared = []
5
6    @classmethod
7    def teardown_class(cls):
8        cls.shared.clear()
9
10    def setup_method(self):
11        self.value = 0
12
13    def teardown_method(self):
14        self.value = None
15
16    def test_increment(self):
17        self.value += 1
18        assert self.value == 1

These hooks are fine for simple cases, especially when porting old unittest-style code. The limitation is that they are less composable than fixtures and do not integrate as naturally with dependency injection across the test suite.

Choose Based on Scope

A good rule is:

  • use function-scoped fixtures for per-test state
  • use class-scoped fixtures for expensive shared setup
  • use xUnit hooks only when they genuinely make the class clearer

That approach keeps tests readable and avoids accidental coupling between methods.

Avoid Sharing Mutable State Unless Necessary

One of the biggest mistakes in class-based test code is storing mutable state on self or cls and then letting tests depend on previous tests having run first.

Bad pattern:

python
1class TestBadState:
2    @classmethod
3    def setup_class(cls):
4        cls.items = []
5
6    def test_add_item(self):
7        self.items.append("x")
8        assert len(self.items) == 1
9
10    def test_starts_empty(self):
11        assert self.items == []

This suite is order-sensitive. That is exactly what pytest tries to help you avoid.

Common Pitfalls

  • Using class variables for mutable shared state when tests really need fresh data per method.
  • Writing teardown code that never runs because the setup raised an exception before the cleanup path was structured correctly.
  • Choosing xUnit hooks by habit even when fixtures would be more reusable and explicit.
  • Assuming self persists between tests in pytest. Each test method gets a new instance.
  • Building tests that depend on execution order instead of isolated setup.

Summary

  • In pytest, fixtures are usually the best way to handle setup and teardown.
  • Use function-scoped fixtures for isolated per-test resources.
  • Use class-scoped fixtures when setup is expensive and safe to share within a class.
  • xUnit-style hooks such as setup_method still work, but they are usually less flexible than fixtures.
  • Keep tests isolated and avoid mutable shared state unless there is a strong reason to share it.

Course illustration
Course illustration

All Rights Reserved.