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

Влияет ли грамотность написания кода на возможность тестирования?

2.3 Middle🔥 241 комментариев
#Архитектура и паттерны#Тестирование

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

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

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

Влияет ли грамотность кода на тестируемость?

Да, критически важно! Качество кода напрямую влияет на возможность и сложность тестирования.

Плохой код - сложно тестировать

Пример 1: Тесная связанность

# ПЛОХО: класс зависит от конкретных реализаций
class UserService:
    def __init__(self):
        self.db = PostgreSQL()  # Прямая зависимость!
        self.email = GmailSender()  # Прямая зависимость!
    
    def create_user(self, email):
        user = self.db.save({"email": email})  # Реальная БД
        self.email.send("Welcome")  # Реальная почта
        return user

# Невозможно тестировать без реальной БД и почты!

Пример 2: Смешанная логика

# ПЛОХО: бизнес логика смешана с деталями реализации
def process_payment(amount):
    # Валидация
    if amount <= 0:
        raise ValueError()
    
    # Доступ в БД
    conn = sqlite3.connect("db.sqlite3")
    cursor = conn.cursor()
    cursor.execute("INSERT INTO payments ...")
    
    # HTTP запрос
    response = requests.post("https://payment-api.com/charge", ...)
    
    # Логирование
    with open("/var/log/payments.log", "a") as f:
        f.write(f"Payment: {amount}")
    
    return response.json()

# Где начинается логика? Где её тестировать?

Хороший код - легко тестировать

Пример 1: Инъекция зависимостей

# ХОРОШО: зависимости передаются
class UserService:
    def __init__(self, db, email_sender):
        self.db = db  # Может быть real или mock
        self.email = email_sender  # Может быть real или mock
    
    def create_user(self, email):
        user = self.db.save({"email": email})
        self.email.send("Welcome")
        return user

# В тестах
class MockDB:
    def save(self, data):
        return {"id": 1, **data}

class MockEmail:
    def send(self, message):
        pass

# Легко тестировать!
db = MockDB()
email = MockEmail()
service = UserService(db, email)
user = service.create_user("john@example.com")
assert user["id"] == 1

Пример 2: Разделение ответственности

# ХОРОШО: каждый класс отвечает за одно
class PaymentValidator:
    def validate(self, amount):
        if amount <= 0:
            raise ValueError("Invalid amount")
        return True

class PaymentProcessor:
    def __init__(self, gateway):
        self.gateway = gateway
    
    def process(self, amount):
        return self.gateway.charge(amount)

class PaymentLogger:
    def log(self, amount, status):
        print(f"Payment: {amount}, Status: {status}")

# Тестируем каждую часть отдельно
validator = PaymentValidator()
assert validator.validate(100) == True

gateway = MockGateway()
processor = PaymentProcessor(gateway)
assert processor.process(100)["status"] == "success"

logger = PaymentLogger()
logger.log(100, "success")  # Легко тестировать

SOLID принципы улучшают тестируемость

Single Responsibility

# ПЛОХО: один класс делает всё
class Order:
    def calculate_total(self):  # Расчёт
        pass
    def save_to_db(self):  # Сохранение
        pass
    def send_email(self):  # Отправка
        pass
    def generate_pdf(self):  # PDF генерация
        pass

# ХОРОШО: каждый класс за одно
class OrderCalculator:
    def calculate_total(self):
        pass

class OrderRepository:
    def save(self):
        pass

class OrderNotifier:
    def send_email(self):
        pass

class InvoiceGenerator:
    def generate_pdf(self):
        pass

Dependency Inversion

# ПЛОХО: зависит от конкретной реализации
class PaymentService:
    def __init__(self):
        self.payment_gateway = StripeGateway()  # Конкретный класс!

# ХОРОШО: зависит от абстракции
from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    @abstractmethod
    def charge(self, amount): pass

class PaymentService:
    def __init__(self, gateway: PaymentGateway):  # Абстракция
        self.gateway = gateway

# В тестах
class TestPaymentGateway(PaymentGateway):
    def charge(self, amount):
        return {"status": "success"}

service = PaymentService(TestPaymentGateway())

Практические примеры тестирования

До улучшений (невозможно тестировать)

# user_service.py
import requests
import sqlite3

def register_user(email, password):
    # Валидация
    if not email or not password:
        return {"error": "Invalid input"}
    
    # Доступ в БД
    conn = sqlite3.connect("app.db")
    cursor = conn.cursor()
    cursor.execute("INSERT INTO users (email, password) VALUES (?, ?)",
                  (email, password))
    conn.commit()
    
    # Отправка письма
    response = requests.post("https://email-api.com/send", 
                            data={"to": email, "subject": "Welcome"})
    
    return {"status": "success"}

# Тест невозможен без реальной БД и API!

После улучшений (легко тестировать)

# domain/user.py - Бизнес логика
class User:
    def __init__(self, email, password):
        if not email or not password:
            raise ValueError("Invalid input")
        self.email = email
        self.password = password

# application/user_service.py - Use cases
class UserService:
    def __init__(self, user_repository, email_service):
        self.repository = user_repository
        self.email = email_service
    
    def register(self, email, password):
        user = User(email, password)  # Валидация в domain
        self.repository.save(user)  # Сохранение через интерфейс
        self.email.send_welcome(email)  # Отправка через интерфейс
        return user

# tests/test_user_service.py
class MockRepository:
    def save(self, user):
        self.saved_user = user
        return user

class MockEmail:
    def send_welcome(self, email):
        self.emails.append(email)

def test_register_user():
    repo = MockRepository()
    email = MockEmail()
    service = UserService(repo, email)
    
    user = service.register("john@example.com", "password123")
    
    assert user.email == "john@example.com"
    assert repo.saved_user == user
    assert "john@example.com" in email.emails

Признаки плохо тестируемого кода

  1. Жёсткие зависимости - зависит от конкретных классов
  2. Глобальное состояние - использует глобальные переменные
  3. Побочные эффекты - вызывает внешние API в функции
  4. Смешанная логика - бизнес логика + деталь реализации
  5. Большие объекты - класс делает слишком много
  6. Сложные конструкторы - много зависимостей, сложно создать
  7. Приватные методы - невозможно тестировать части
  8. Статические методы - сложно мокировать

Инструменты тестирования

# unittest (встроенный)
import unittest

class TestUserService(unittest.TestCase):
    def test_register(self):
        pass

# pytest (популярный)
import pytest

def test_register():
    assert True

# pytest-mock (мокирование)
from unittest.mock import Mock, patch

@patch("requests.post")
def test_with_mock(mock_post):
    mock_post.return_value = {"status": "success"}
    # Тестируем с мокированным запросом

Метрики тестируемости

- Code Coverage: > 90% (idealistically)
- Cyclomatic Complexity: < 10 (простые функции)
- Dependency Injection: >= 80% (минимум зависимостей)
- Unit Tests: один на функцию/метод
- Integration Tests: один на сценарий

Итоговый вывод

Грамотность кода ПРЯМО влияет на тестируемость:

  • Инъекция зависимостей → легко мокировать
  • Разделение ответственности → легко тестировать части
  • Чистая архитектура → нет побочных эффектов
  • SOLID принципы → гибко и тестируемо

Плохой код - значит никакие тесты не спасут! Хороший код - легко тестировать, меньше багов в production.