Что такое модульное тестирование (Unit Testing)?

Ответ

Модульное тестирование (Unit Testing) — это уровень тестирования, направленный на проверку корректности работы наименьших изолируемых частей программы (юнитов) — обычно функций, методов или классов — в отрыве от остальной системы и внешних зависимостей (баз данных, сетевых запросов, файловой системы).

Ключевые принципы и характеристики:

  • Изоляция: Юнит-тест должен проверять только логику одного модуля. Внешние зависимости заменяются заглушками (stubs) или имитаторами (mocks).
  • Скорость: Юнит-тесты должны выполняться очень быстро (миллисекунды), чтобы их можно было запускать часто.
  • Детерминированность: Результат теста всегда одинаков для одних и тех же входных данных.
  • Автоматизация: Тесты пишутся в коде и запускаются автоматически (часто в CI/CD).

Пример модульного теста с использованием фреймворка pytest и моков (unittest.mock):

# Файл: calculator.py
class Calculator:
    def add(self, a, b):
        return a + b

# Файл: service.py
class DataService:
    def fetch_external_data(self):
        # Дорогой или нестабильный вызов внешнего API
        return 42

class ProcessingService:
    def __init__(self, calculator, data_service):
        self.calc = calculator
        self.data_svc = data_service

    def process(self, x, y):
        external_value = self.data_svc.fetch_external_data()
        return self.calc.add(x, y) + external_value

# Файл: test_processing_service.py
import pytest
from unittest.mock import Mock
from service import ProcessingService
from calculator import Calculator

def test_process_with_mocks():
    # 1. Создаем мок-объекты для зависимостей
    mock_calculator = Mock(spec=Calculator)
    mock_data_service = Mock()

    # 2. Настраиваем поведение моков
    mock_calculator.add.return_value = 10  # Мок метода add
    mock_data_service.fetch_external_data.return_value = 5  # Мок внешнего вызова

    # 3. Создаем тестируемый объект, передавая моки
    service = ProcessingService(mock_calculator, mock_data_service)

    # 4. Выполняем тестируемый метод
    result = service.process(3, 7)

    # 5. Проверяем результат и взаимодействие с моками (assertions)
    assert result == 15  # 10 (от мока калькулятора) + 5 (от мока сервиса данных)
    mock_calculator.add.assert_called_once_with(3, 7)  # Проверяем, что add вызван с правильными аргументами
    mock_data_service.fetch_external_data.assert_called_once()  # Проверяем, что метод вызван

Популярные фреймворки для модульного тестирования:

  • Python: pytest, unittest
  • JavaScript/TypeScript: Jest, Mocha + Chai
  • Java: JUnit, TestNG
  • C#: NUnit, xUnit.net, MSTest

Ответ 18+ 🔞

А, модульное тестирование, говоришь? Ну это ж, блядь, святое дело! Это когда ты берешь свою прогу, которая вроде работает, но внутри у неё, как у старого "Запорожца", всё скрипит и может в любой момент накрыться медным тазом, и начинаешь её по косточкам разбирать.

Представь, у тебя есть функция, которая складывает два числа. Вроде проще некуда, да? А потом ты подсовываешь ей не числа, а, например, строку и null. И она тебе вместо того, чтобы вежливо сказать "иди нахуй, чувак, это не числа", выдает какую-то дичь, и вся твоя крутая система падает, как пьяный мужик с лавочки. Вот чтобы этого не было, и нужны юнит-тесты. Это такие маленькие, блядь, сторожевые псы для каждого кусочка кода.

Суть, если по-простому:

  • Изоляция, ёпта! Это главное. Ты тестируешь одну маленькую функцию так, будто вокруг неё вакуум. База данных? Нахуй! Сетевое АПИ, которое отвечает раз в полчаса? В пизду! Всё это заменяется на подставные уроды — стабы (stubs) и моки (mocks). Это как в кино дублеры: выглядят похоже, но падать и бить морду будут по сценарию.
  • Скорость — пиздец какая! Эти тесты должны бегать быстрее, чем соседский кот от моего тапка. Миллисекунды. Чтобы ты мог запустить их сотню раз за день и не захотеть повеситься от ожидания.
  • Предсказуемость. Один и тот же тест сегодня, завтра и послезавтра должен давать один и тот же результат. Если нет — ты где-то накосячил, либо твой код ведет себя как хитрая жопа.
  • Автоматизация. Ты их написал, настроил, и они сами бегают, как ебушки-воробушки, каждый раз, когда ты что-то меняешь. Идеально — встроить в свою цепочку сборки (CI/CD), чтобы код, который ломает тесты, даже не думал попасть к другим людям.

Вот смотри, как это выглядит в жизни. Берем Python и pytest:

# Файл: calculator.py - тут наш "юнит", простой как три копейки
class Calculator:
    def add(self, a, b):
        return a + b

# Файл: service.py - а тут уже посложнее, с зависимостями
class DataService:
    def fetch_external_data(self):
        # Это типа запрос к какому-нибудь медленному и кривому АПИ
        # Которое может и не ответить, и ответить ерундой
        return 42

class ProcessingService:
    def __init__(self, calculator, data_service):
        self.calc = calculator
        self.data_svc = data_service

    def process(self, x, y):
        external_value = self.data_svc.fetch_external_data() # Опа, зависимость!
        return self.calc.add(x, y) + external_value # И ещё одна!

# Файл: test_processing_service.py - а вот тут начинается магия и подставы
import pytest
from unittest.mock import Mock
from service import ProcessingService
from calculator import Calculator

def test_process_with_mocks():
    # 1. Делаем подставных уродов! Моки, блядь!
    mock_calculator = Mock(spec=Calculator) # Притворяется калькулятором
    mock_data_service = Mock() # Притворяется сервисом данных

    # 2. Говорим им, как они должны себя вести. Дрессируем, короче.
    mock_calculator.add.return_value = 10  # "Как бы" сложил и вернул 10
    mock_data_service.fetch_external_data.return_value = 5 # "Как бы" сходил в АПИ и принес 5

    # 3. Собираем нашего тестируемого франкенштейна, сую ему моки вместо настоящих органов
    service = ProcessingService(mock_calculator, mock_data_service)

    # 4. Запускаем!
    result = service.process(3, 7)

    # 5. А теперь проверяем, блядь! Сошлось ли?
    assert result == 15  # 10 (от мока-калькулятора) + 5 (от мока-данных) = 15. Логика?
    # А ещё проверяем, что моков не просто так дергали:
    mock_calculator.add.assert_called_once_with(3, 7)  # Калькулятору скормили именно 3 и 7?
    mock_data_service.fetch_external_data.assert_called_once()  # А в АПИ вообще сходили?

Вот и вся кухня. Написал такие тесты — и спишь спокойно. Поменял что-то в одном месте, запустил тесты — если они все зеленые, значит, ты ничего не сломал. Если красные — э, сабака сука, бошка думай, где накосячил.

Из популярного, что используют:

  • Питон: pytest (царь и бог), unittest (дедушка стандартный).
  • Жабаскрипт/Тайпскрипт: Jest (всеобщий любимец), Mocha + Chai (старая гвардия).
  • Java: JUnit (классика жанра).
  • C#: NUnit, xUnit.net (ну, ты понял).

Короче, без этого жить можно, но это как ходить по охуенно тонкому льду. Рано или поздно провалишься.