← Назад к вопросам

Какие знаешь методы для unit-тестирования?

1.0 Junior🔥 191 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Методы для unit-тестирования в Python

Unit-тестирование — это тестирование отдельных компонентов кода в изоляции. Расскажу о методах, инструментах и best practices, которые использую в работе.

1. Основные фреймворки

pytest (рекомендуемый)

Модерный, гибкий, с отличными возможностями расширения.

# test_calculator.py
import pytest

def add(a, b):
    return a + b

# Простой тест
def test_add_positive_numbers():
    assert add(2, 3) == 5

# Параметризованный тест
@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (-1, 1, 0),
    (0, 0, 0),
])
def test_add_various(a, b, expected):
    assert add(a, b) == expected

# Тест на исключение
def test_add_invalid_input():
    with pytest.raises(TypeError):
        add("a", 1)

# Фикстуры
@pytest.fixture
def sample_data():
    return {"a": 2, "b": 3}

def test_with_fixture(sample_data):
    assert add(sample_data["a"], sample_data["b"]) == 5

Запуск:

# Все тесты
pytest

# Конкретный файл
pytest test_calculator.py

# С verbose и coverage
pytest -v --cov=myapp

# В watch mode
pytest-watch

unittest (встроенный)

Входит в стандартную библиотеку Python. Более формальный стиль.

import unittest

class TestCalculator(unittest.TestCase):
    def setUp(self):
        """Выполняется перед каждым тестом"""
        self.calculator = Calculator()
    
    def tearDown(self):
        """Выполняется после каждого теста"""
        self.calculator = None
    
    def test_add(self):
        result = self.calculator.add(2, 3)
        self.assertEqual(result, 5)
    
    def test_divide_by_zero(self):
        with self.assertRaises(ValueError):
            self.calculator.divide(5, 0)

if __name__ == '__main__':
    unittest.main()

2. Mocking и Patching

Изоляция компонента от внешних зависимостей.

Mock объекты

from unittest.mock import Mock, MagicMock, patch

# Mock базовый
def test_with_mock():
    mock_db = Mock()
    mock_db.get_user.return_value = {"id": 1, "name": "Alice"}
    
    user = mock_db.get_user(1)
    assert user["name"] == "Alice"
    mock_db.get_user.assert_called_once_with(1)

# MagicMock — более функциональный
def test_with_magic_mock():
    mock_api = MagicMock()
    mock_api.fetch_data.return_value = [{"id": 1}]
    
    # Можем проверить аргументы
    assert mock_api.fetch_data.call_count >= 0
    mock_api.fetch_data.assert_called_with()

Patch декоратор

from unittest.mock import patch

# Патчим на уровне модуля
@patch('requests.get')
def test_fetch_data(mock_get):
    mock_get.return_value.status_code = 200
    mock_get.return_value.json.return_value = {"id": 1}
    
    # Здесь requests.get — это mock
    from mymodule import fetch_user
    result = fetch_user(1)
    assert result["id"] == 1
    mock_get.assert_called_once()

# Context manager для временного патча
def test_with_context_manager():
    with patch('builtins.open', create=True) as mock_open:
        mock_open.return_value.__enter__.return_value.read.return_value = "data"
        # код, использующий open()

pytest-mock (рекомендуется)

def test_with_pytest_mock(mocker):
    # Проще синтаксис
    mock_api = mocker.MagicMock()
    mock_api.get_data.return_value = {"status": "ok"}
    
    # Патчим функцию
    mocker.patch('mymodule.external_api', mock_api)
    
    result = external_api.get_data()
    assert result["status"] == "ok"

3. Работа с БД в тестах

Использование fixtures

@pytest.fixture
def db_session():
    """Создаёт сессию для каждого теста"""
    session = create_session()
    yield session
    session.rollback()  # Откатываем изменения

@pytest.fixture
def user_factory():
    """Factory для создания тестовых пользователей"""
    def _create_user(name="Test User", email="test@example.com"):
        return User(name=name, email=email)
    return _create_user

def test_user_creation(db_session, user_factory):
    user = user_factory(name="Alice")
    db_session.add(user)
    db_session.commit()
    
    fetched = db_session.query(User).filter_by(name="Alice").first()
    assert fetched.email == "test@example.com"

Тестирование с реальной БД (рекомендуется для интеграционных тестов)

import pytest
from django.test import TestCase

class UserRepositoryTest(TestCase):
    @classmethod
    def setUpClass(cls):
        """Выполняется один раз перед всеми тестами класса"""
        super().setUpClass()
        # Миграции уже запущены Django
    
    def setUp(self):
        """Выполняется перед каждым тестом"""
        self.user = User.objects.create(name="Alice", email="alice@example.com")
    
    def test_user_exists(self):
        user = User.objects.get(id=self.user.id)
        assert user.name == "Alice"

4. Тестирование асинхронного кода

asyncio

import pytest
import asyncio

@pytest.mark.asyncio
async def test_async_function():
    result = await async_operation()
    assert result == expected_value

@pytest.fixture
def event_loop():
    """Для pytest-asyncio"""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()

Тестирование Telegram ботов

from aiogram.dispatcher import Dispatcher
from aiogram.types import Update, User as TgUser, Chat, Message
from aiogram.types import CallbackQuery

@pytest.mark.asyncio
async def test_start_handler(dp: Dispatcher):
    # Создаём fake update
    message = Message(
        message_id=1,
        date=0,
        chat=Chat(id=123, type="private"),
        from_user=TgUser(id=123, is_bot=False, first_name="Test")
    )
    message.text = "/start"
    
    update = Update(update_id=1, message=message)
    
    # Отправляем update в dispatcher
    await dp.feed_update(dp.bot, update)
    # Проверяем результат

5. Test-Driven Development (TDD)

Пишу тест ДО кода:

# Шаг 1: RED — пишу падающий тест
def test_user_can_change_password():
    user = User(password="old_password")
    user.change_password("old_password", "new_password")
    assert user.check_password("new_password")

# Шаг 2: GREEN — пишу минимальный код
class User:
    def __init__(self, password):
        self.password = password
    
    def change_password(self, old, new):
        if self.check_password(old):
            self.password = new
    
    def check_password(self, pwd):
        return self.password == pwd

# Шаг 3: REFACTOR — улучшаю код
from werkzeug.security import generate_password_hash, check_password_hash

class User:
    def __init__(self, password):
        self.password_hash = generate_password_hash(password)
    
    def change_password(self, old, new):
        if self.check_password(old):
            self.password_hash = generate_password_hash(new)
    
    def check_password(self, pwd):
        return check_password_hash(self.password_hash, pwd)

6. Покрытие тестами (Coverage)

# Запустить с отчётом о покрытии
pytest --cov=myapp --cov-report=html

# Проверить конкретные файлы
pytest --cov=myapp.models test_models.py
# В коде: пропустить строку если нужно
def debug_only():  # pragma: no cover
    print("Debug info")

7. Тестирование исключений

# pytest
def test_error_handling():
    with pytest.raises(ValueError, match="Invalid input"):
        process_data(invalid_data)

# unittest
def test_error_handling(self):
    self.assertRaises(ValueError, process_data, invalid_data)
    # или
    with self.assertRaises(ValueError):
        process_data(invalid_data)

8. Fixtures для комплексных сценариев

@pytest.fixture
def authenticated_client(client):
    """Клиент с авторизованным пользователем"""
    user = User.objects.create(username="testuser", password="testpass")
    client.force_login(user)
    return client

@pytest.fixture
def api_client():
    """REST API клиент"""
    from rest_framework.test import APIClient
    return APIClient()

@pytest.fixture(params=["en", "ru", "de"])
def language(request):
    """Параметризованная фикстура для разных языков"""
    return request.param

def test_multilingual(language):
    # Тест запустится 3 раза с разными языками
    pass

9. Лучшие практики

# 1. Один assert на тест (желательно)
def test_user_creation():
    user = User.create(name="Alice")
    assert user.name == "Alice"

# 2. Использовать descriptive names
def test_user_cannot_login_with_wrong_password():
    pass

# 3. Arrange-Act-Assert
def test_discount_calculation():
    # Arrange
    order = Order(total=100)
    discount = VipDiscount()
    
    # Act
    result = discount.apply(order)
    
    # Assert
    assert result == 90  # 10% discount

# 4. Избегать флакИ тестов
# ПЛОХО: зависит от времени
def test_time_based():
    current = datetime.now().hour
    assert current >= 0  # Может быть false в некоторых случаях

# ХОРОШО: мокируем время
@patch('mymodule.datetime')
def test_time_based(mock_datetime):
    mock_datetime.now.return_value = datetime(2024, 1, 1, 12, 0)
    # Тест всегда deterministic

10. Примеры реальных тестов

# Django ORM тест
def test_user_repository_find_by_email(db):
    user = User.objects.create(name="Alice", email="alice@example.com")
    
    found = User.objects.get(email="alice@example.com")
    assert found.id == user.id

# API тест
def test_get_user_endpoint(api_client):
    user = User.objects.create(name="Alice", email="alice@example.com")
    
    response = api_client.get(f"/api/users/{user.id}/")
    assert response.status_code == 200
    assert response.data["name"] == "Alice"

# Service тест с mocks
def test_user_service_send_welcome_email(mocker):
    mock_email = mocker.patch('myapp.services.send_email')
    service = UserService()
    
    service.register_user("alice@example.com")
    
    mock_email.assert_called_once()
    args = mock_email.call_args
    assert "welcome" in args[1]["subject"].lower()

Вывод

Лучший набор инструментов для unit-тестирования:

  • pytest как основной фреймворк
  • pytest-mock для мокирования
  • pytest-cov для проверки покрытия
  • pytest-asyncio для асинхронного кода
  • TDD подход для качественного кода
  • Mock объекты для изоляции компонентов
  • Минимум 90% покрытия для production кода

Это обеспечивает надёжность, поддерживаемость и уверенность в коде.

Какие знаешь методы для unit-тестирования? | PrepBro