Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Нужны ли тесты всегда?
Это частый вопрос в индустрии, и ответ не чёрно-белый: тесты нужны почти всегда, но степень их покрытия зависит от контекста проекта.
Когда тесты критичны
Production-среда и критичные системы:
- Финансовые транзакции (каждый баг = потеря денег)
- Медицинские системы (каждая ошибка может стоить жизни)
- Системы с SLA (требуются гарантии)
- Долгоживущие проекты (техдолг растёт без тестов)
Сложная бизнес-логика:
# Расчёты скидок, комиссий, налогов — всегда нужны тесты
def calculate_price(base_price: float, discount: float, tax_rate: float) -> float:
"""Даже простая функция должна протестирована для граничных случаев"""
discounted = base_price * (1 - discount / 100)
with_tax = discounted * (1 + tax_rate / 100)
return round(with_tax, 2)
# Граничные случаи:
# - discount = 0, 100 (полная скидка)
# - negative values (ошибочные данные)
# - precision (округление)
Публичные API и библиотеки:
- Клиенты зависят от вашего кода
- Любое изменение API может сломать их
- Регрессионные тесты предотвращают неожиданные breaking changes
Когда можно обойтись без полного покрытия
MVP и экспериментальный код:
# Быстрый прототип для валидации идеи
# Здесь фокус на скорости разработки, не стабильности
def get_trending_posts():
return requests.get(https://api.example.com/trending).json()
Простая логика представления (UI/шаблоны):
- Рендеринг HTML без сложной логики
- Фронтенд без бизнес-правил
- UI слой можно тестировать инструментами E2E (Playwright, Cypress)
Конфигурационные файлы:
# Конфиг-файл — обычно достаточно manual testing
CONFIG = {
database_url: postgresql://...,
debug: True,
}
Мой подход: прагматичное тестирование
Слои приложения и тестовое покрытие:
-
Domain/Business Logic: 90-100% покрытие
# Сложная логика = максимум тестов class OrderService: def calculate_total(self, items: List[Item]) -> Money: pass # Граничные случаи, скидки, налоги -
Application/Use Cases: 80-90% покрытие
# Orchestration logic class CreateOrderUseCase: def execute(self, command: CreateOrderCommand) -> OrderDTO: pass # Happy path + error cases -
Infrastructure/External APIs: 60-80% покрытие
# Внешние зависимости — мокируем и тестируем class PaymentGateway: def charge(self, card: str, amount: Money) -> TransactionId: pass # Тестируем с VCR.py cassettes -
Presentation/Handlers: 50-70% покрытие
# UI слой — основной юнит-тесты, E2E для критичных flows @router.post(/orders) def create_order(request: CreateOrderRequest): pass
Техники эффективного тестирования
Pyramidal Testing (тестовая пирамида):
E2E (5-10%)
Integration (15-25%)
Unit (70-80%)
Много дешёвых юнит-тестов, меньше интеграционных, ещё меньше E2E.
Test-Driven Development (TDD):
# 1. RED — напишу падающий тест
def test_order_total_with_discount():
order = Order(items=[Item(10)], discount=10) # 10%
assert order.total == 9.0 # Falls
# 2. GREEN — минимальный код
class Order:
def __init__(self, items, discount=0):
self.items = items
self.discount = discount
@property
def total(self):
subtotal = sum(i.price for i in self.items)
return subtotal * (1 - self.discount / 100)
# 3. REFACTOR — улучшаю сохраняя зелёные тесты
Когда НЕ писать тесты (редко, но бывает)
- Одноразовые скрипты (миграция данных, backfill)
- Spike/прототипирование (потом удалится)
- Код, который сразу выбросишь (но лучше не полагаться на это)
Вывод
Проверь себя вопросом:
- Может ли баг в этом коде стоить денег? → Нужны тесты
- Зависит ли от этого код других? → Нужны тесты
- Будешь ли развивать это дальше? → Нужны тесты
- Это throwaway код? → Можно обойтись
На production я пишу тесты для:
- Всей бизнес-логики (90%+)
- Критичных flows (e2e)
- API контрактов
Не тестирую:
- Банальный CRUD без логики
- Зависимости, которые уже протестированы (requests, sqlalchemy)
- UI ренденю (делаю визуальное тестирование)
Тесты — это не overhead, это страховка. На проектах, где я видел 0% тестов, через год техдолг зашкаливал, а в production падали баги каждую неделю.