Что такое Mock-объект в тестировании?

Ответ

Mock-объект (заглушка) — это специально созданный объект, который имитирует поведение реальной зависимости (например, базы данных, сетевого сервиса, файловой системы) в модульных тестах. Его цель — изолировать тестируемый код от внешних систем, чтобы тесты были быстрыми, стабильными и предсказуемыми.

Зачем это нужно в C++ проектах:

  1. Скорость: Замена медленной БД или сетевого вызова быстрой заглушкой.
  2. Стабильность: Тесты не зависят от доступности внешних сервисов или состояния БД.
  3. Фокусировка: Тестируется только логика конкретного модуля, а не его зависимостей.
  4. Проверка взаимодействий: Mock позволяет убедиться, что тестируемый код корректно вызывает методы зависимостей с правильными аргументами.

Пример с использованием Google Mock (gmock): Допустим, у нас есть класс DataProcessor, который зависит от интерфейса IDatabase.

// 1. Интерфейс зависимости
class IDatabase {
public:
    virtual ~IDatabase() = default;
    virtual bool connect(const std::string& connectionString) = 0;
    virtual std::vector<int> fetchImportantData() const = 0;
};

// 2. Реальный класс, который мы хотим протестировать
class DataProcessor {
    IDatabase* db;
public:
    DataProcessor(IDatabase* database) : db(database) {}

    int calculateSum() {
        if (!db->connect("my_db")) {
            throw std::runtime_error("Connection failed");
        }
        auto data = db->fetchImportantData();
        return std::accumulate(data.begin(), data.end(), 0);
    }
};

// 3. Создаем Mock-класс, реализующий интерфейс (обычно с помощью макросов GMOCK)
#include <gmock/gmock.h>
class MockDatabase : public IDatabase {
public:
    MOCK_METHOD(bool, connect, (const std::string&), (override));
    MOCK_METHOD(std::vector<int>, fetchImportantData, (), (const, override));
};

// 4. Пишем модульный тест с использованием Mock
#include <gtest/gtest.h>
TEST(DataProcessorTest, CalculateSumSuccess) {
    // Arrange
    MockDatabase mockDb;
    DataProcessor processor(&mockDb);

    // Настраиваем ожидания: метод connect будет вызван 1 раз с любым аргументом и вернет true
    EXPECT_CALL(mockDb, connect(testing::_))
        .Times(1)
        .WillOnce(testing::Return(true));

    // Настраиваем: метод fetchImportantData вернет заданный вектор
    std::vector<int> fakeData = {1, 2, 3, 4, 5};
    EXPECT_CALL(mockDb, fetchImportantData())
        .Times(1)
        .WillOnce(testing::Return(fakeData));

    // Act
    int result = processor.calculateSum();

    // Assert
    EXPECT_EQ(result, 15); // 1+2+3+4+5
    // Google Mock автоматически проверит, что все ожидаемые вызовы (EXPECT_CALL) произошли.
}

Отличие Mock от других заглушек:

  • Stub — просто возвращает заранее заданные данные, не проверяет вызовы.
  • Fake — имеет упрощенную, но рабочую реализацию (например, база данных в памяти).
  • Mock — как в примере выше: настраивает ожидания и проверяет, как его методы были вызваны.

В C++ экосистеме популярны фреймворки для мокирования: Google Mock (часть Google Test), FakeIt, Trompeloeil.

Ответ 18+ 🔞

А, ну вот, опять эти ваши моки! Слушай, давай я тебе на пальцах объясню, что это за зверь такой, а то смотрю, у тебя глаза уже стекленеют.

Mock-объект — это, по сути, такая хитрая жопа, которая притворяется настоящей зависимостью. Представь, тебе надо протестировать кофемашину, а она подключена к водопроводу. Так вот, мок — это как если бы ты вместо водопровода прикрутил бутылку с водой и сказал: «Кофемашина, вот тебе вода, делай что должен, а я посмотрю, как ты там внутри крутишь свои шестерёнки». Цель — изолировать твой код от всей этой внешней ерунды, чтобы тесты летали как угорелые и не падали из-за того, что у соседа интернет отрубили.

Зачем это в C++? Да похуй, на самом деле, на язык, причины везде одни и те же:

  1. Скорость, ёпта! Твой тест не будет ждать, пока база данных на другом конце света выполнит запрос. Заглушка отработает за микросекунды.
  2. Стабильность. Нет зависимости от того, лежит ли сервис, обновили ли схему в БД или просто сисадмин решил всё перезагрузить. Тест зависит только от твоего кода.
  3. Фокусировка. Ты тестируешь именно свою логику, а не то, как криво написан чужой API.
  4. Проверка взаимодействия. Это самое сольное. Ты можешь не просто дать моку данные, а проверить, что твой код вызвал нужный метод, с нужными аргументами и нужное количество раз. Доверия ебать ноль ко всему, что движется, вот принцип.

Смотри пример на Google Mock (gmock). Допустим, есть у нас какой-то DataProcessor, который тычет палкой в базу данных через интерфейс IDatabase.

// 1. Интерфейс, который все должны реализовать
class IDatabase {
public:
    virtual ~IDatabase() = default;
    virtual bool connect(const std::string& connectionString) = 0;
    virtual std::vector<int> fetchImportantData() const = 0;
};

// 2. Наш герой, которого будем тестировать
class DataProcessor {
    IDatabase* db;
public:
    DataProcessor(IDatabase* database) : db(database) {}

    int calculateSum() {
        if (!db->connect("my_db")) {
            throw std::runtime_error("Connection failed");
        }
        auto data = db->fetchImportantData();
        return std::accumulate(data.begin(), data.end(), 0);
    }
};

// 3. А вот и наш МОК! Он наследует интерфейс, но ведёт себя как мы скажем.
#include <gmock/gmock.h>
class MockDatabase : public IDatabase {
public:
    MOCK_METHOD(bool, connect, (const std::string&), (override));
    MOCK_METHOD(std::vector<int>, fetchImportantData, (), (const, override));
};

// 4. Сам тест, где мы и играем в кукловода
#include <gtest/gtest.h>
TEST(DataProcessorTest, CalculateSumSuccess) {
    // Arrange (Подготовка)
    MockDatabase mockDb; // Создали заглушку
    DataProcessor processor(&mockDb); // Скормили её нашему процессору

    // Настраиваем ожидания! Вот магия:
    // "Ожидаю, что метод connect вызовут ровно 1 раз с любым аргументом (testing::_), и тогда верни true"
    EXPECT_CALL(mockDb, connect(testing::_))
        .Times(1)
        .WillOnce(testing::Return(true));

    // "А метод fetchImportantData вызовут тоже 1 раз, и тогда верни вот этот вектор"
    std::vector<int> fakeData = {1, 2, 3, 4, 5};
    EXPECT_CALL(mockDb, fetchImportantData())
        .Times(1)
        .WillOnce(testing::Return(fakeData));

    // Act (Действие) - запускаем логику
    int result = processor.calculateSum();

    // Assert (Проверка)
    EXPECT_EQ(result, 15); // 1+2+3+4+5 = 15
    // И тут Google Mock сам, в фоне, проверит, а сработали ли все EXPECT_CALL.
    // Если наш DataProcessor не вызвал connect или вызвал его дважды — тест упадёт!
}

Чем мок не является, чтобы не было путаницы:

  • Stub (Заглушка) — тупая, как пробка. Просто возвращает то, что ты в неё запихнул. Не проверяет, звали её или нет. Как бутылка с водой для кофемашины.
  • Fake (Фейк) — уже умнее, у него есть какая-то своя, упрощённая логика. Типа база данных, которая хранит всё в оперативке. Работает по-настоящему, но для тестов.
  • Mock (Мок) — вот этот наш хитрый ублюдок. Он не только данные подсовывает, но ещё и следит, как с ним взаимодействуют, и яростно ругается, если что-то пошло не по плану.

В мире C++ для этого дела есть свои инструменты: Google Mock (идет в комплекте с Google Test), FakeIt, Trompeloeil. Выбирай любой, суть одна — терпения ноль ебать ждать, пока внешний мир соизволит ответить.