Нужно ли покрывать тестами весь код проекта?

«Нужно ли покрывать тестами весь код проекта?» — вопрос из категории Тестирование, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нет, стремиться к 100% покрытию тестами всего кода — контрпродуктивно. Цель тестирования — снижать риски и повышать уверенность в изменениях, а не достигать абстрактных метрик. Ресурсы следует направлять на тестирование наиболее важных и рискованных частей системы.

Что обязательно нужно тестировать (высокая отдача):

  • Бизнес-логика и доменные правила: Алгоритмы расчётов, валидация, сложные преобразования данных.
  • Критические пути (Happy Path): Основные сценарии использования приложения.
  • Сложные условия и ветвления: Код с множеством if/else, switch.
  • Интеграционные точки: Взаимодействие с базами данных, внешними API, файловыми системами (через абстракции).
  • Код, подверженный частым изменениям: Чтобы рефакторинг и новые фичи не ломали существующую функциональность.

Что можно не тестировать или тестировать минимально (низкая отдача):

  • Простые геттеры/сеттеры или свойства автореализации без дополнительной логики.
  • Шаблонный код, сгенерированный инструментами (например, DTO, части ORM).
  • Тривиальные мапперы или конвертеры, где связь между входом и выходом очевидна и прямолинейна.
  • Внешние библиотеки и фреймворки. Предполагается, что они уже протестированы своими разработчиками. Нужно тестировать интеграцию с ними, а не их внутреннюю работу.

Пример приоритизации:

// ВЫСОКИЙ ПРИОРИТЕТ для тестов: Сложная бизнес-логика.
public class DiscountCalculator
{
    public decimal Calculate(User user, Order order)
    {
        if (user.IsPremium && order.Amount > 1000)
            return order.Amount * 0.85m; // Скидка 15%
        if (order.Items.Count > 5)
            return order.Amount * 0.9m; // Скидка 10%
        return order.Amount;
    }
}
// Для этого метода нужны юнит-тесты, проверяющие все ветки условий.

// НИЗКИЙ ПРИОРИТЕТ для тестов: Простой маппер данных.
public class UserDtoMapper
{
    public UserDto MapToDto(User entity) => new UserDto 
    { 
        Id = entity.Id, // Прямое присваивание
        Name = entity.Name 
    };
}
// Тест для этого метода, по сути, будет дублировать его код, принося мало пользы.

Практическое правило: Стремитесь к 70-85% покрытия по branch/condition для ключевых модулей. Это обычно обеспечивает хороший баланс между надежностью и затратами на поддержку тестов. Качество тестов (их читаемость, поддержка и способность ловить реальные баги) всегда важнее процента покрытия.