← Назад к вопросам
Как сделать очистку ресурсов после выхода из теста в pytest?
2.0 Middle🔥 181 комментариев
#Python Core#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Очистка ресурсов после выхода из теста в pytest
Тесты часто создают временные ресурсы (файлы, БД, сокеты, подключения). Их нужно аккуратно очищать, чтобы избежать утечек и побочных эффектов между тестами. Расскажу о методах.
Метод 1: Fixtures с yield (рекомендуется)
import pytest
import tempfile
import os
@pytest.fixture
def temp_file():
"""Fixture, которая создаёт файл и удаляет его после теста"""
# Setup — создание ресурса
temp_fd, temp_path = tempfile.mkstemp()
os.close(temp_fd)
print(f"\nCreated temp file: {temp_path}")
yield temp_path # Тест получает путь
# Teardown — очистка ресурса
if os.path.exists(temp_path):
os.remove(temp_path)
print(f"\nDeleted temp file: {temp_path}")
def test_write_to_file(temp_file):
"""Тест использует fixture"""
with open(temp_file, "w") as f:
f.write("test data")
# Файл автоматически удалится после теста
assert os.path.exists(temp_file)
# Вывод:
# Created temp file: /tmp/xxx
# PASSED
# Deleted temp file: /tmp/xxx
Метод 2: Fixture с scope (для переиспользования между тестами)
import pytest
import tempfile
import shutil
@pytest.fixture(scope="module")
def temp_dir():
"""Создать директорию на время всего модуля"""
temp_path = tempfile.mkdtemp()
print(f"\nCreating {temp_path} for entire module")
yield temp_path
# Очистить после всех тестов модуля
shutil.rmtree(temp_path)
print(f"\nDeleted {temp_path}")
def test_one(temp_dir):
file_path = f"{temp_dir}/file1.txt"
with open(file_path, "w") as f:
f.write("data1")
assert os.path.exists(file_path)
def test_two(temp_dir):
# Та же директория!
file_path = f"{temp_dir}/file2.txt"
with open(file_path, "w") as f:
f.write("data2")
assert os.path.exists(file_path)
Элементы scope:
- function (default) — новый ресурс для каждого теста
- class — один ресурс на класс тестов
- module — один ресурс на модуль
- session — один ресурс на всю сессию
Метод 3: Pytest fixtures с autouse
import pytest
@pytest.fixture(autouse=True)
def cleanup():
"""Автоматически запустится перед каждым тестом"""
print("\nSetup: initializing resources")
yield # Тест выполняется
print("\nTeardown: cleaning resources")
def test_one():
print("Running test_one")
assert True
def test_two():
print("Running test_two")
assert True
# Вывод:
# Setup: initializing resources
# Running test_one
# Teardown: cleaning resources
# Setup: initializing resources
# Running test_two
# Teardown: cleaning resources
Метод 4: Setup и Teardown методы в классах
import pytest
class TestDatabaseOperations:
def setup_method(self):
"""Выполняется перед каждым методом"""
self.db = DatabaseConnection(":memory:")
self.db.create_tables()
print("\nDatabase setup")
def teardown_method(self):
"""Выполняется после каждого метода"""
self.db.close()
print("\nDatabase teardown")
def test_insert(self):
self.db.insert("users", {"id": 1, "name": "John"})
assert self.db.count("users") == 1
def test_update(self):
self.db.insert("users", {"id": 1, "name": "John"})
self.db.update("users", {"id": 1}, {"name": "Jane"})
user = self.db.get("users", 1)
assert user["name"] == "Jane"
class TestFileOperations:
@classmethod
def setup_class(cls):
"""Выполняется один раз перед всеми методами"""
cls.temp_dir = tempfile.mkdtemp()
print(f"\nCreating {cls.temp_dir}")
@classmethod
def teardown_class(cls):
"""Выполняется один раз после всех методов"""
shutil.rmtree(cls.temp_dir)
print(f"\nDeleted {cls.temp_dir}")
def test_write(self):
file_path = f"{self.temp_dir}/test.txt"
with open(file_path, "w") as f:
f.write("test")
assert os.path.exists(file_path)
Метод 5: Context managers для управления ресурсами
from contextlib import contextmanager
import tempfile
import shutil
@contextmanager
def temporary_directory():
"""Context manager для временной директории"""
temp_path = tempfile.mkdtemp()
try:
yield temp_path
finally:
shutil.rmtree(temp_path)
def test_with_context_manager():
with temporary_directory() as temp_dir:
file_path = f"{temp_dir}/file.txt"
with open(file_path, "w") as f:
f.write("test")
assert os.path.exists(file_path)
# После выхода из блока — всё очищено
assert not os.path.exists(temp_dir)
Метод 6: Очистка подключений к БД
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
@pytest.fixture
def db_session():
"""Fixture для тестирования с БД"""
# Setup: создать БД
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
# Teardown: очистить
session.close()
engine.dispose()
def test_user_creation(db_session):
user = User(name="John", email="john@example.com")
db_session.add(user)
db_session.commit()
result = db_session.query(User).first()
assert result.name == "John"
Метод 7: Очистка моков и патчей
from unittest.mock import patch, MagicMock
import pytest
@pytest.fixture
def mock_api():
"""Fixture с mocking"""
with patch("requests.get") as mock_get:
mock_get.return_value.json.return_value = {"status": "ok"}
yield mock_get
# После теста mock автоматически удалится
def test_api_call(mock_api):
import requests
response = requests.get("https://api.example.com")
assert response.json()["status"] == "ok"
mock_api.assert_called_once()
Метод 8: Обработка исключений при очистке
import pytest
@pytest.fixture
def resource_with_cleanup():
"""Fixture с обработкой ошибок при очистке"""
resource = None
try:
# Setup
resource = create_expensive_resource()
yield resource
except Exception:
# Если тест упал, всё равно очистим
if resource:
safe_cleanup(resource)
raise
finally:
# Гарантированная очистка
if resource:
cleanup_resource(resource)
def test_with_resource(resource_with_cleanup):
# Даже если тест упадёт, очистка выполнится
assert resource_with_cleanup is not None
Метод 9: Наблюдение за утечками ресурсов
import pytest
import gc
import tracemalloc
@pytest.fixture(autouse=True)
def memory_check():
"""Проверить утечки памяти"""
tracemalloc.start()
snapshot_before = tracemalloc.take_snapshot()
yield
snapshot_after = tracemalloc.take_snapshot()
top_stats = snapshot_after.compare_to(snapshot_before, "lineno")
# Показать top 3 утечки
print("\nTop memory allocations:")
for stat in top_stats[:3]:
print(stat)
tracemalloc.stop()
def test_memory_intensive_operation():
data = [i for i in range(1_000_000)]
assert len(data) == 1_000_000
Best Practices
- Используй yield вместо return в fixtures — гарантирует очистку
- Используй autouse=True для общих очисток — не нужно передавать в каждый тест
- Выбирай правильный scope — function, class, module или session
- Обрабатывай ошибки при очистке — используй try/finally
- Тестируй очистку — проверяй, что ресурсы действительно удаляются
- Используй context managers — удобно и безопасно
- Логируй операции setup/teardown — для отладки
- Не оставляй побочные эффекты — каждый тест должен быть независим
- Используй tmpdir и tmp_path fixtures из pytest — встроенные для временных файлов
Встроенные fixtures pytest
def test_with_tmpdir(tmpdir):
"""tmpdir — встроенная fixture для временных файлов"""
file_path = tmpdir.join("test.txt")
file_path.write("content")
assert file_path.read() == "content"
# Автоматически удалится после теста
def test_with_tmp_path(tmp_path):
"""tmp_path — более современная версия (pathlib)"""
file_path = tmp_path / "test.txt"
file_path.write_text("content")
assert file_path.read_text() == "content"
# Автоматически удалится
Правильная очистка ресурсов — это основа надёжных и быстрых тестов!