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

Какие проблемы могут быть у функции при покрытии 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 сценарии
Какие проблемы могут быть у функции при покрытии Unit тестами? | PrepBro