← Назад к вопросам
Какие проблемы могут быть у функции при покрытии Unit тестами?
1.3 Junior🔥 121 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Проблемы функции при unit тестировании
Даже если функция покрыта тестами на 100%, это не гарантирует качество. Есть много скрытых проблем, которые unit тесты могут не поймать.
1. Слишком узкая область тестирования
# Функция с проблемой
def parse_date(date_string):
"""Парсит дату формата YYYY-MM-DD"""
parts = date_string.split("-")
return {"year": int(parts[0]), "month": int(parts[1]), "day": int(parts[2])}
# Плохие тесты (100% покрытие, но неполные)
def test_parse_date():
result = parse_date("2025-03-22")
assert result["year"] == 2025 # ✅ Работает
# Что не протестировали:
parse_date("") # IndexError
parse_date("2025/03/22") # ValueError (неверный формат)
parse_date("2025-13-01") # Месяц > 12!
parse_date("2025-02-30") # Февраль не имеет 30 дней!
parse_date(None) # TypeError
2. Неполное тестирование граничных случаев (Edge Cases)
# Функция с ошибкой на граничных значениях
def divide_safely(a, b):
"""Безопасное деление"""
if b == 0:
return None
return a / b
# Недостаточные тесты
def test_divide():
assert divide_safely(10, 2) == 5 # OK
assert divide_safely(10, 0) is None # OK
# Забыли тесты на:
divide_safely(-10, 2) # Отрицательные числа
divide_safely(0, 5) # Ноль в числителе
divide_safely(10, -2) # Отрицательный делитель
divide_safely(10.5, 2.5) # Float числа
divide_safely(10, 0.0000001) # Очень близко к нулю (floating point precision)
3. Сложные зависимости (Dependencies)
# Функция с внешними зависимостями
import requests
def get_user_data(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()
# Проблема: unit тесты мокируют запрос
from unittest.mock import patch
@patch('requests.get')
def test_get_user_data(mock_get):
mock_get.return_value.json.return_value = {"id": 1, "name": "John"}
result = get_user_data(1)
assert result["name"] == "John"
# ❌ Но в реальности API может:
# - Возвращать разные форматы
# - Быть недоступным
# - Возвращать ошибку 500
# - Временно зависать
# - Возвращать пусто после добавления поля
# Мок скрывает эти проблемы!
4. Проблемы с состоянием (State)
# Функция, зависящая от глобального состояния
cache = {}
def get_cached_data(key):
if key in cache:
return cache[key]
# Получить из БД
data = database.query(key)
cache[key] = data
return data
# Unit тесты проходят отдельно
def test_get_cached_data():
result = get_cached_data("user:1")
assert result == {"id": 1}
# Но когда много тестов:
# - Кэш остаётся между тестами
# - Один тест может загрязнить кэш для другого
# - Порядок выполнения тестов влияет на результат
5. Недостаточное тестирование исключений
def process_payment(amount):
"""Обработать платёж"""
if amount <= 0:
raise ValueError("Amount must be positive")
if amount > 10000:
raise ValueError("Amount too large")
return {"status": "success", "amount": amount}
# Плохие тесты
def test_process_payment():
result = process_payment(100)
assert result["status"] == "success"
# Забыли тестить исключения!
# Правильные тесты
import pytest
def test_process_payment_invalid_amount():
with pytest.raises(ValueError, match="Amount must be positive"):
process_payment(-10)
with pytest.raises(ValueError, match="Amount too large"):
process_payment(15000)
6. Логика, которая не выполняется
def complex_logic(a, b, c):
"""Сложная логика"""
if a > 0:
if b > 0:
if c > 0:
return a + b + c
else:
return a + b - c
else:
return a - b
else:
return b * c
# Даже при 100% line coverage могут быть нетестированные пути
# Code coverage инструмент считает строку покрытой, но:
# - Не все условия проверены
# - Не все комбинации значений протестированы
# Нужна Branch Coverage (покрытие ветвей), не только Line Coverage
7. Асинхронный код
import asyncio
async def fetch_data(url):
"""Получить данные асинхронно"""
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
# Проблемы при тестировании:
def test_fetch_data(): # ❌ Это не работает!
result = fetch_data("https://api.example.com")
assert result is not None
# asyncio требует специального обращения
@pytest.mark.asyncio
async def test_fetch_data(): # ✅ Правильно
result = await fetch_data("https://api.example.com")
assert result is not None
8. Проблемы производительности
def find_substring(text, substring):
"""Найти подстроку (с проблемой производительности)"""
for i in range(len(text)):
match = True
for j in range(len(substring)):
if text[i + j] != substring[j]:
match = False
break
if match:
return i
return -1
# Unit тесты проходят быстро
def test_find_substring():
assert find_substring("hello", "ll") == 2
assert find_substring("hello", "x") == -1
# Но с большими строками:
# O(n*m) алгоритм будет очень медленным
# Unit тесты не выявили проблему производительности!
9. Race conditions в многопоточном коде
from threading import Thread
counter = 0
def increment_counter():
global counter
for _ in range(1000000):
counter += 1 # ❌ Race condition!
# Unit тесты могут пройти
def test_increment():
global counter
counter = 0
thread1 = Thread(target=increment_counter)
thread2 = Thread(target=increment_counter)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
# ❌ counter != 2000000 из-за race condition
# Но тест может проходить случайно!
10. Проблемы с временем
import time
from datetime import datetime
def is_promotion_active():
"""Акция активна с 10 до 18 часов"""
hour = datetime.now().hour
return 10 <= hour < 18
# Unit тесты зависят от текущего времени!
def test_is_promotion_active():
# Этот тест пройдёт только если запустить между 10-18
result = is_promotion_active()
# Невозможно протестировать стабильно
# Правильный подход с мокингом времени
from unittest.mock import patch
@patch('datetime.datetime')
def test_is_promotion_active_morning(mock_datetime):
mock_datetime.now.return_value.hour = 9
assert is_promotion_active() == False
@patch('datetime.datetime')
def test_is_promotion_active_afternoon(mock_datetime):
mock_datetime.now.return_value.hour = 15
assert is_promotion_active() == True
11. Проблемы с base64/хешированием
import hashlib
def hash_password(password):
"""Хешировать пароль"""
return hashlib.sha256(password.encode()).hexdigest()
# Unit тест
def test_hash_password():
result = hash_password("password123")
assert len(result) == 64 # SHA256 всегда 64 символа
# ✅ Проходит
# Но проблема: этот хеш детерминированный!
# ❌ Небезопасно для паролей (нужна соль!)
# Unit тесты не выявили уязвимость
12. Проблемы интеграции
# Функция работает отдельно
def calculate_total(items):
return sum(item['price'] * item['quantity'] for item in items)
# Unit тесты проходят
def test_calculate_total():
assert calculate_total([{"price": 10, "quantity": 2}]) == 20
# Но при интеграции с DB:
# - items может быть None
# - items может содержать дополнительные поля
# - price может быть string из CSV
# - Расчёты с float имеют проблемы точности
Как решить эти проблемы?
# 1. Используй разные типы тестов
- Unit тесты (изолированно)
- Integration тесты (с реальными зависимостями)
- E2E тесты (полный workflow)
# 2. Правильные метрики покрытия
from pytest_cov import cov
# Branch coverage важнее чем line coverage
pytest --cov=mymodule --cov-report=term-missing:skip-covered
# 3. Граничные случаи
def test_with_edge_cases():
# Пусто
assert func([]) == expected
# Один элемент
assert func([1]) == expected
# Максимальное значение
assert func([MAX_VALUE]) == expected
# Отрицательные
assert func([-1]) == expected
# 4. Property-based тесты
from hypothesis import given, strategies as st
@given(st.lists(st.integers()))
def test_func_property(items):
"""Функция должна работать с ANY списком целых чисел"""
result = func(items)
assert result is not None
Резюме
100% code coverage НЕ означает качественный код!
Проверяй:
- Edge cases и граничные значения
- Все ветви кода (branch coverage)
- Исключения и error handling
- Интеграцию с другим кодом
- Производительность
- Асинхронность и threading
- Зависимость от времени
- Real-world сценарии