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

Встречал ли плохие тесты

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

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

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

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

Встречал ли плохие тесты

Да, очень часто. Плохие тесты создают больше проблем чем тесты вообще не быть.

Типы плохих тестов

1. Тесты, которые не работают (flaky tests)

import asyncio
import time

# ПЛОХО: тест иногда падает, иногда проходит
def test_user_notification():
    user = create_user()
    send_email(user.email)
    time.sleep(0.1)  # Надеемся что email пришёл
    assert email_was_sent(user.email)
    # Иногда падает если сеть медленная!

# ХОРОШО: явно ждём или мокируем
def test_user_notification():
    with patch('send_email') as mock:
        user = create_user()
        send_email(user.email)
        mock.assert_called_once()

2. Тесты с побочными эффектами

# ПЛОХО: тесты зависят друг от друга и порядка выполнения
class TestUser:
    def test_create_user(self):
        global user_id
        user = User.objects.create(name='John')
        user_id = user.id
    
    def test_get_user(self):
        # Зависит от предыдущего теста!
        user = User.objects.get(id=user_id)
        assert user.name == 'John'

# ХОРОШО: каждый тест независим
class TestUser:
    def setup_method(self):
        self.user = User.objects.create(name='John')
    
    def test_create_user(self):
        assert self.user.id is not None
    
    def test_get_user(self):
        user = User.objects.get(id=self.user.id)
        assert user.name == 'John'

3. Тесты без assertions

# ПЛОХО: просто запускает код, не проверяет результат
def test_user_creation():
    user = User.objects.create(name='John')
    # Что мы проверяем? Ничего!

# ХОРОШО: явные assertions
def test_user_creation():
    user = User.objects.create(name='John')
    assert user.id is not None
    assert user.name == 'John'
    assert User.objects.filter(name='John').exists()

4. Тесты которые тестируют реализацию, а не поведение

# ПЛОХО: тесты деталь реализации
def test_calculate_discount():
    user = User(is_premium=True)
    discount = calculate_discount(100, user)
    # Проверяем внутреннюю переменную!
    assert user._discount_cache == 0.2

# ХОРОШО: проверяем поведение
def test_calculate_discount():
    user = User(is_premium=True)
    discount = calculate_discount(100, user)
    assert discount == 80  # Проверяем результат

5. Тесты которые тестируют слишком много

# ПЛОХО: один тест для всей функциональности
def test_order_processing():
    user = create_user()
    product = create_product()
    order = create_order(user, product)
    order.process()
    assert order.is_completed
    assert user.email_sent
    assert inventory_updated
    assert payment_processed
    assert notification_sent
    # Если что-то сломалось - не понять что

# ХОРОШО: маленькие focused тесты
def test_order_marked_as_completed():
    order = create_order()
    order.process()
    assert order.is_completed

def test_order_sends_email():
    with patch('send_email') as mock:
        order = create_order()
        order.process()
        mock.assert_called()

def test_order_updates_inventory():
    with patch('update_inventory') as mock:
        order = create_order()
        order.process()
        mock.assert_called()

6. Тесты которые тестируют третьи стороны

# ПЛОХО: тестируем поведение Django (уже протестировано)
def test_user_model():
    user = User.objects.create(name='John')
    assert User.objects.filter(name='John').exists()
    # Django уже протестировал это!

# ХОРОШО: тестируем наш код
def test_user_name_required():
    with pytest.raises(ValidationError):
        User.objects.create(name='')  # Наше правило валидации

def test_user_email_validation():
    with pytest.raises(ValidationError):
        User.objects.create(email='invalid')  # Наше правило

7. Неправильная изоляция (no mocks)

# ПЛОХО: вызываем реальное API
def test_user_signup():
    response = requests.post('https://api.example.com/users', {
        'name': 'John'
    })
    # Если API недоступно - тест падает
    # Если API изменился - тест падает
    # Медленно и ненадёжно
    assert response.status_code == 201

# ХОРОШО: мокируем API
@patch('requests.post')
def test_user_signup(mock_post):
    mock_post.return_value.status_code = 201
    response = requests.post(...)
    assert response.status_code == 201

8. Хрупкие тесты (brittle tests)

# ПЛОХО: зависит от точного сообщения об ошибке
def test_invalid_email():
    with pytest.raises(ValidationError) as exc:
        validate_email('invalid')
    # Если сообщение изменится - тест сломается
    assert str(exc.value) == 'Invalid email format'

# ХОРОШО: проверяем сам факт исключения
def test_invalid_email():
    with pytest.raises(ValidationError):
        validate_email('invalid')

9. Тесты без setup/teardown

# ПЛОХО: оставляет мусор в БД
class TestUser:
    def test_user_creation(self):
        user = User.objects.create(name='Test')
        # Тест пройдён
        # Но пользователь остался в БД!

# ХОРОШО: очищает за собой
class TestUser:
    def test_user_creation(self):
        user = User.objects.create(name='Test')
        assert user.id is not None
        # teardown/fixture удалит пользователя
    
    # Или явно
    def test_user_creation_explicit(self):
        user = User.objects.create(name='Test')
        assert user.id is not None
        user.delete()

10. Тесты которые не тестируют edge cases

# ПЛОХО: только happy path
def test_divide():
    assert divide(10, 2) == 5

# ХОРОШО: тестируем edge cases
def test_divide():
    assert divide(10, 2) == 5  # Happy path
    assert divide(0, 1) == 0  # Edge case
    with pytest.raises(ValueError):
        divide(10, 0)  # Error case
    assert divide(-10, 2) == -5  # Negative
    assert divide(0.5, 0.2) == pytest.approx(2.5)  # Float

Признаки плохих тестов

# 1. Тесты часто падают случайно
# Причина: зависимостью от времени, порядка

# 2. Сложнее поддерживать тесты чем код
# Признак: тесты тестируют реализацию

# 3. Нельзя запустить в разном порядке
# Признак: побочные эффекты

# 4. Не понять что тест проверяет
# Признак: плохо написан или слишком сложный

# 5. Все обновляют одновременно
# Признак: привязаны к реализации

# 6. Запуск занимает часов
# Признак: нет изоляции, много I/O

Как писать хорошие тесты

# 1. Arrange - подготовка
def test_discount_calculation():
    user = User(is_premium=True)
    product = Product(price=100)
    
    # 2. Act - действие
    discount = calculate_discount(product, user)
    
    # 3. Assert - проверка
    assert discount == 20

# 4. Cleanup - очистка (если нужна)
def teardown_method(self):
    User.objects.all().delete()

Метрики качества тестов

# Хорошие тесты:
# 1. Fast - < 1ms на один тест (unit)
# 2. Isolated - не зависит от других
# 3. Repeatable - всегда результат одинаков
# 4. Self-validating - true/false, не нужна интерпретация
# 5. Timely - написаны до или одновременно с кодом

# Плохие тесты не соответствуют этому

Refactoring плохих тестов

# БЫЛО
def test_everything():
    user = create_user()
    post = create_post(user)
    like = like_post(post, user)
    assert like.is_active
    assert post.likes_count == 1
    assert user.recent_activity_updated

# СТАЛО
def test_like_is_active_after_creation():
    like = create_like()
    assert like.is_active

def test_post_likes_count_increments():
    post = create_post()
    create_like(post)
    assert post.likes_count == 1

def test_user_activity_updated_on_like():
    user = create_user()
    before_activity = user.last_activity
    create_like(user=user)
    assert user.last_activity > before_activity

Заключение

  • Плохие тесты хуже чем отсутствие тестов
  • Причины: медленные, ненадёжные, сложные
  • Признаки: часто падают, зависят друг от друга, тестируют реализацию
  • Решение: писать маленькие, focused, изолированные тесты
  • Правило: если тест сложнее чем код - что-то не так
Встречал ли плохие тесты | PrepBro