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

Ответ

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

Ключевые принципы (FIRST):

  • Fast (Быстрые): Тесты должны выполняться за доли секунды.
  • Isolated (Изолированные): Тест не должен зависеть от других тестов, внешних сервисов (БД, API) или состояния системы. Зависимости заменяются заглушками (mocks/stubs).
  • Repeatable (Повторяемые): Результат теста должен быть одинаковым при каждом запуске.
  • Self-validating (Самопроверяемые): Тест должен однозначно определять, пройден он или нет (Pass/Fail).
  • Timely (Своевременные): Пишутся до или параллельно с кодом (TDD).

Пример теста с использованием xUnit и Moq: Допустим, у нас есть сервис, который зависит от репозитория.

// Производственный код
public class OrderService
{
    private readonly IOrderRepository _repository;
    public OrderService(IOrderRepository repository) => _repository = repository;

    public Order GetOrder(int id) => _repository.GetById(id);
}

// Тест
public class OrderServiceTests
{
    [Fact]
    public void GetOrder_ValidId_ReturnsOrder()
    {
        // 1. ARRANGE (Подготовка)
        var expectedOrder = new Order { Id = 1, Total = 100 };
        var mockRepo = new Mock<IOrderRepository>(); // Создаём mock-объект
        mockRepo.Setup(repo => repo.GetById(1)).Returns(expectedOrder); // Настраиваем поведение

        var service = new OrderService(mockRepo.Object); // Внедряем зависимость

        // 2. ACT (Действие)
        var result = service.GetOrder(1);

        // 3. ASSERT (Проверка)
        Assert.NotNull(result);
        Assert.Equal(expectedOrder.Id, result.Id);
        Assert.Equal(expectedOrder.Total, result.Total);
        // Проверяем, что метод репозитория был вызван ровно один раз с правильным аргументом
        mockRepo.Verify(repo => repo.GetById(1), Times.Once);
    }
}

Популярные фреймворки для .NET: xUnit, NUnit, MSTest. Для создания заглушек — Moq, NSubstitute.

Ответ 18+ 🔞

Давай разберём эту модульную хуйню, чтобы ты наконец перестал писать код, который падает от чиха.

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

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

  • Fast (Быстрые): Тесты должны бежать как ошпаренный кот. Если ты успеваешь сходить за кофе, пока они выполняются — ты всё сделал не так. Доли секунды, Карл!
  • Isolated (Изолированные): Это святое. Твой тест не должен зависеть ни от чего: ни от других тестов, ни от того, что в базе данных, ни от погоды на Марсе. Всё, что не твоя функция, — заменяешь заглушками (mocks, stubs). Полная изоляция, как в инфекционном боксе.
  • Repeatable (Повторяемые): Запустил раз, запустил десять, запустил в полночь на пятницу — результат должен быть одинаковым. Если тест то проходит, то нет — это не тест, а рулетка, и ты её организатор.
  • Self-validating (Самопроверяемые): Тест сам должен крикнуть "Ура!" или "Всё пропало!". Никаких "посмотри в лог и сам реши". Либо зелёная галочка, либо красный крест. Всё.
  • Timely (Своевременные): Идеально — писать их ДО основного кода (это тот самый TDD, который все хвалят, но мало кто делает). На практике — хотя бы параллельно. Писать тесты через полгода к коду, который уже всё ебал и сломал, — это как надевать презерватив после того, как кончил.

Ну а теперь пример, чтобы стало совсем понятно. Смотри, есть у нас сервис заказов, который тыкается в репозиторий. В тесте мы этот репозиторий подменяем на муляж, чтобы не лезть в реальную базу.

// Это наш рабочий код, который мы тестируем
public class OrderService
{
    private readonly IOrderRepository _repository;
    public OrderService(IOrderRepository repository) => _repository = repository;

    public Order GetOrder(int id) => _repository.GetById(id);
}

// А это наши тесты, где мы всё ломаем и проверяем
public class OrderServiceTests
{
    [Fact]
    public void GetOrder_ValidId_ReturnsOrder()
    {
        // 1. ARRANGE (Подготовка, или "расчехляемся")
        var expectedOrder = new Order { Id = 1, Total = 100 };
        var mockRepo = new Mock<IOrderRepository>(); // Вот он, наш муляж-подставка!
        mockRepo.Setup(repo => repo.GetById(1)).Returns(expectedOrder); // Говорим муляжу: "Когда вызовут GetById с единичкой, верни вот этот фейковый заказ".

        var service = new OrderService(mockRepo.Object); // Пихаем муляж в сервис.

        // 2. ACT (Действие, или "нажимаем на курок")
        var result = service.GetOrder(1);

        // 3. ASSERT (Проверка, или "а не обосрались ли мы?")
        Assert.NotNull(result);
        Assert.Equal(expectedOrder.Id, result.Id);
        Assert.Equal(expectedOrder.Total, result.Total);
        // И самое важное: проверяем, что наш сервис вообще тыкнулся в репозиторий, как мы и ожидали.
        mockRepo.Verify(repo => repo.GetById(1), Times.Once);
    }
}

Вот и вся магия. Из популярного в .NET: для тестов бери xUnit, NUnit или MSTest (xUnit сейчас модный). А чтобы создавать эти самые заглушки — Moq или NSubstitute. Выбирай что нравится, главное — начни это использовать, а не просто читать об этом, а то так и будешь дебажить свою шнягу в три часа ночи, ебать колотить.