Ответ
Mock-объект (заглушка) — это специально созданный объект, который имитирует поведение реальной зависимости (например, базы данных, сетевого сервиса, файловой системы) в модульных тестах. Его цель — изолировать тестируемый код от внешних систем, чтобы тесты были быстрыми, стабильными и предсказуемыми.
Зачем это нужно в C++ проектах:
- Скорость: Замена медленной БД или сетевого вызова быстрой заглушкой.
- Стабильность: Тесты не зависят от доступности внешних сервисов или состояния БД.
- Фокусировка: Тестируется только логика конкретного модуля, а не его зависимостей.
- Проверка взаимодействий: 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++? Да похуй, на самом деле, на язык, причины везде одни и те же:
- Скорость, ёпта! Твой тест не будет ждать, пока база данных на другом конце света выполнит запрос. Заглушка отработает за микросекунды.
- Стабильность. Нет зависимости от того, лежит ли сервис, обновили ли схему в БД или просто сисадмин решил всё перезагрузить. Тест зависит только от твоего кода.
- Фокусировка. Ты тестируешь именно свою логику, а не то, как криво написан чужой API.
- Проверка взаимодействия. Это самое сольное. Ты можешь не просто дать моку данные, а проверить, что твой код вызвал нужный метод, с нужными аргументами и нужное количество раз. Доверия ебать ноль ко всему, что движется, вот принцип.
Смотри пример на 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. Выбирай любой, суть одна — терпения ноль ебать ждать, пока внешний мир соизволит ответить.