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

Какие умеешь писать тесты?

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

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

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

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

Какие тесты я умею писать

Тестирование — это искусство и наука. Я имею опыт написания различных видов тестов и понимаю когда и какой тип использовать. Давайте разберемся во всех видах тестов.

Пирамида тестов

           E2E (End-to-End)  - 5-10%
          /\
        /    \
      /        \      Integration Tests - 15-20%
    /            \
  /                \  Unit Tests - 70-80%

1. Unit Tests (Модульные тесты)

Цель: Тестировать отдельные функции/методы в изоляции

import pytest
from unittest.mock import Mock, patch
from myapp.services import UserService
from myapp.models import User

class TestUserService:
    """Тесты для UserService"""
    
    @pytest.fixture
    def user_service(self):
        """Fixture для создания UserService"""
        return UserService()
    
    def test_create_user_success(self, user_service):
        """Успешное создание пользователя"""
        # Arrange
        user_data = {"email": "john@example.com", "name": "John"}
        
        # Act
        result = user_service.create_user(**user_data)
        
        # Assert
        assert result.email == "john@example.com"
        assert result.name == "John"
    
    def test_create_user_invalid_email(self, user_service):
        """Проверка валидации email"""
        with pytest.raises(ValueError, match="Invalid email"):
            user_service.create_user(email="invalid-email", name="John")
    
    def test_get_user_by_id(self, user_service):
        """Получение пользователя по ID"""
        # Создаем тестовые данные
        user = user_service.create_user(email="jane@example.com", name="Jane")
        
        # Получаем пользователя
        fetched = user_service.get_user(user.id)
        
        # Проверяем
        assert fetched.id == user.id
        assert fetched.email == "jane@example.com"
    
    def test_delete_user(self, user_service):
        """Удаление пользователя"""
        user = user_service.create_user(email="temp@example.com", name="Temp")
        user_id = user.id
        
        user_service.delete_user(user_id)
        
        with pytest.raises(User.DoesNotExist):
            user_service.get_user(user_id)

2. Тесты с мокированием (Mocking)

Цель: Изолировать компонент подменяя его зависимости

from unittest.mock import Mock, patch, MagicMock
from myapp.services import PaymentService
from myapp.external.payment_gateway import StripeGateway

class TestPaymentService:
    """Тесты для платежного сервиса"""
    
    def test_process_payment_with_mock(self):
        """Тестирование с мокированным Stripe"""
        # Создаем mock
        mock_gateway = Mock(spec=StripeGateway)
        mock_gateway.charge.return_value = {'status': 'success', 'charge_id': '123'}
        
        # Внедряем mock
        service = PaymentService(gateway=mock_gateway)
        
        # Выполняем операцию
        result = service.process_payment(amount=100, card_token='tok_visa')
        
        # Проверяем результат
        assert result['status'] == 'success'
        
        # Проверяем что метод был вызван с правильными параметрами
        mock_gateway.charge.assert_called_once_with(100, 'tok_visa')
    
    def test_payment_retry_on_failure(self):
        """Проверка retry логики"""
        mock_gateway = Mock(spec=StripeGateway)
        # Первый вызов - ошибка, второй - успех
        mock_gateway.charge.side_effect = [
            Exception("Temporary error"),
            {'status': 'success', 'charge_id': '456'}
        ]
        
        service = PaymentService(gateway=mock_gateway)
        
        # Операция должна переделаться
        result = service.process_payment_with_retry(amount=100, card_token='tok_visa')
        assert result['status'] == 'success'
        assert mock_gateway.charge.call_count == 2

3. Integration Tests (Интеграционные тесты)

Цель: Тестировать взаимодействие нескольких компонентов

import pytest
from django.test import TestCase, Client
from myapp.models import User, Order

class TestOrderIntegration(TestCase):
    """Интеграционные тесты для заказов"""
    
    def setUp(self):
        """Подготовка перед каждым тестом"""
        self.user = User.objects.create_user(
            email='test@example.com',
            password='password123'
        )
        self.client = Client()
    
    def test_create_order_workflow(self):
        """Полный процесс создания заказа"""
        # 1. Авторизация
        self.client.login(email='test@example.com', password='password123')
        
        # 2. Создание заказа
        response = self.client.post('/api/v1/orders/', {
            'items': [{'product_id': 1, 'quantity': 2}],
            'shipping_address': {'city': 'Moscow'}
        }, content_type='application/json')
        
        # 3. Проверяем ответ
        assert response.status_code == 201
        order_id = response.json()['id']
        
        # 4. Проверяем БД
        order = Order.objects.get(id=order_id)
        assert order.user == self.user
        assert order.total_items == 2
        
        # 5. Проверяем статус
        response = self.client.get(f'/api/v1/orders/{order_id}/')
        assert response.json()['status'] == 'pending'

4. E2E Tests (End-to-End тесты) с Playwright

Цель: Тестировать полный пользовательский сценарий через браузер

import pytest
from playwright.sync_api import sync_playwright, expect

class TestUserRegistrationFlow:
    """E2E тесты для регистрации пользователя"""
    
    @pytest.fixture
    def browser(self):
        """Фикстура браузера"""
        with sync_playwright() as p:
            browser = p.chromium.launch()
            yield browser
            browser.close()
    
    def test_user_can_register_and_login(self, browser):
        """Пользователь может зарегистрироваться и войти"""
        page = browser.new_page()
        
        # 1. Переходим на страницу регистрации
        page.goto('http://localhost:3000/register')
        
        # 2. Заполняем форму
        page.fill('input[name="email"]', 'newuser@example.com')
        page.fill('input[name="password"]', 'SecurePass123!')
        page.fill('input[name="confirm_password"]', 'SecurePass123!')
        
        # 3. Отправляем форму
        page.click('button[type="submit"]')
        
        # 4. Ожидаем редирект
        page.wait_for_url('**/login')
        
        # 5. Логинимся
        page.fill('input[name="email"]', 'newuser@example.com')
        page.fill('input[name="password"]', 'SecurePass123!')
        page.click('button[type="submit"]')
        
        # 6. Проверяем что вошли в систему
        expect(page).to_have_url('**/dashboard')
        expect(page.locator('text=Welcome, newuser')).to_be_visible()
        
        page.close()

5. Snapshot Tests (Тесты снимков)

Цель: Проверить что вывод не изменился

import pytest
from myapp.serializers import UserSerializer

def test_user_serialization_snapshot(snapshot):
    """Проверить формат сериализации юзера"""
    user = User(id=1, email='john@example.com', name='John')
    serializer = UserSerializer(user)
    
    # Сравниваем с сохраненным снимком
    assert serializer.data == snapshot

# Первый запуск создает файл:
# tests/__snapshots__/test_serializers.ambr
# При изменении кода - тест ломается, нужно обновить

6. Parametrized Tests (Параметризованные тесты)

Цель: Тестировать одну функцию с разными данными

@pytest.mark.parametrize("input,expected", [
    ("hello", "HELLO"),
    ("WORLD", "WORLD"),
    ("123", "123"),
    ("", ""),
])
def test_uppercase(input, expected):
    """Тестировать функцию с разными значениями"""
    assert input.upper() == expected

# Запустится 4 теста автоматически

@pytest.mark.parametrize("user_id,should_exist", [
    (1, True),
    (999, False),
    (0, False),
])
def test_user_exists(user_id, should_exist):
    exists = User.objects.filter(id=user_id).exists()
    assert exists == should_exist

7. API Tests (Тесты REST API)

Цель: Тестировать HTTP endpoints

import pytest
from rest_framework.test import APIClient
from django.contrib.auth.models import User

class TestUserAPI:
    """Тесты для User API endpoints"""
    
    @pytest.fixture
    def api_client(self):
        return APIClient()
    
    @pytest.fixture
    def user(self):
        return User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='pass123'
        )
    
    def test_list_users(self, api_client):
        """GET /api/users/"""
        response = api_client.get('/api/v1/users/')
        assert response.status_code == 200
        assert isinstance(response.json(), list)
    
    def test_create_user(self, api_client):
        """POST /api/users/"""
        data = {
            'email': 'new@example.com',
            'password': 'pass123'
        }
        response = api_client.post('/api/v1/users/', data)
        assert response.status_code == 201
        assert response.json()['email'] == 'new@example.com'
    
    def test_get_user_detail(self, api_client, user):
        """GET /api/users/1/"""
        response = api_client.get(f'/api/v1/users/{user.id}/')
        assert response.status_code == 200
        assert response.json()['email'] == 'test@example.com'
    
    def test_update_user(self, api_client, user):
        """PUT /api/users/1/"""
        api_client.force_authenticate(user=user)
        response = api_client.put(f'/api/v1/users/{user.id}/', {
            'email': 'updated@example.com'
        })
        assert response.status_code == 200

8. Property-Based Tests (Генеративное тестирование)

Цель: Тестировать с автоматически сгенерированными данными

from hypothesis import given, strategies as st

@given(st.integers())
def test_absolute_value_is_positive(x):
    """Абсолютное значение всегда положительное"""
    assert abs(x) >= 0

@given(st.lists(st.integers()))
def test_sorted_list_is_sorted(lst):
    """Отсортированный список всегда отсортирован"""
    sorted_lst = sorted(lst)
    assert all(sorted_lst[i] <= sorted_lst[i+1] for i in range(len(sorted_lst)-1))

@given(
    st.emails(),
    st.text(min_size=1, max_size=100)
)
def test_user_creation_with_generated_data(email, name):
    """Создание пользователя с сгенерированными данными"""
    user = User.objects.create_user(email=email, name=name)
    assert user.email == email
    assert user.name == name

9. Performance Tests (Тесты производительности)

Цель: Убедиться что операция быстрая

import pytest
import time
from django.test.utils import override_settings

class TestPerformance:
    """Тесты производительности"""
    
    @pytest.mark.benchmark
    def test_large_query_performance(self):
        """Тест скорости выполнения сложного запроса"""
        start = time.time()
        users = User.objects.filter(is_active=True).select_related('profile')[:1000]
        list(users)  # Force evaluation
        elapsed = time.time() - start
        
        # Должно выполниться менее чем за 100ms
        assert elapsed < 0.1
    
    def test_cache_effectiveness(self):
        """Проверка что кэш работает"""
        # Первый запрос - медленно
        start = time.time()
        user1 = get_user_cached(1)
        time1 = time.time() - start
        
        # Второй запрос - из кэша, должен быть быстрее
        start = time.time()
        user2 = get_user_cached(1)
        time2 = time.time() - start
        
        assert time2 < time1 / 10  # В 10 раз быстрее

10. Load Tests (Тесты нагрузки)

Цель: Проверить как система работает под нагрузкой

import locust

class UserBehavior(locust.HttpUser):
    """Симуляция поведения пользователя"""
    wait_time = locust.between(1, 5)
    
    @locust.task(1)
    def list_users(self):
        self.client.get("/api/v1/users/")
    
    @locust.task(2)
    def get_user(self):
        self.client.get("/api/v1/users/1/")
    
    @locust.task(1)
    def create_user(self):
        self.client.post("/api/v1/users/", json={
            'email': 'test@example.com',
            'password': 'pass123'
        })

# Запуск: locust -f locustfile.py

Мой набор инструментов для тестирования

Фреймворки:

  • pytest — основной фреймворк (лучше чем unittest)
  • unittest/mock — встроенные мокирующие инструменты
  • hypothesis — генеративное тестирование
  • locust — load тестирование

Django специфично:

  • pytest-django — интеграция с Django
  • factory-boy — создание тестовых объектов
  • freezegun — мокирование времени
  • responses — мокирование HTTP запросов

Coverage:

  • pytest-cov — измерение покрытия кода
  • Стремлюсь к 90%+ coverage

Best Practices

# 1. Arrange-Act-Assert паттерн
def test_something():
    # Arrange - подготовка
    user = create_test_user()
    
    # Act - действие
    result = user.get_full_name()
    
    # Assert - проверка
    assert result == "John Doe"

# 2. Одно assert на тест (если возможно)
# 3. Тест должен быть быстрым (< 100ms)
# 4. Использовать fixtures для переиспользуемых данных
# 5. Давать понятные имена тестам

Заключение

Я умею писать:

  • Unit tests для отдельных функций
  • Integration tests для взаимодействия компонентов
  • E2E tests для полных пользовательских сценариев
  • API tests для REST endpoints
  • Performance tests для оптимизации
  • Load tests для масштабируемости
  • Parametrized tests для тестирования с разными данными
  • Property-based tests для поиска edge cases

Тестирование — это не то что приходит в конце, это часть разработки. Я пишу тесты ЧТО БЫ разработать (TDD), а не ПОСЛЕ разработки.