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

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

Ответ

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.