Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое Mock в тестировании
Mock — это имитация реального объекта, которая используется для изоляции компонента при тестировании. Это ключевой инструмент для написания быстрых, надёжных unit тестов.
Основное определение
Mock — это объект-заместитель, который:
- Имитирует поведение реального объекта (например, БД, HTTP клиента)
- Позволяет контролировать его входы и выходы
- Проверяет, как тестируемый компонент взаимодействует с этим объектом
- Заменяет медленные/сложные зависимости
Различие между Mock, Stub, Fake и Spy
Это часто путают, но это разные инструменты:
// Интерфейс реальной зависимости
class DatabaseInterface {
public:
virtual ~DatabaseInterface() = default;
virtual User getUser(int id) = 0;
virtual void saveUser(const User& user) = 0;
virtual void deleteUser(int id) = 0;
};
// ============================================
// 1. STUB — просто возвращает заранее подготовленные данные
// ============================================
class StubDatabase : public DatabaseInterface {
public:
User getUser(int id) override {
return User{id, "John", "john@example.com"};
}
void saveUser(const User& user) override {}
void deleteUser(int id) override {}
};
// Используется когда нужны фиксированные ответы
// Не проверяет, вызывались ли методы
// ============================================
// 2. MOCK — проверяет вызовы и их параметры
// ============================================
class MockDatabase : public DatabaseInterface {
public:
MOCK_METHOD(User, getUser, (int id), (override));
MOCK_METHOD(void, saveUser, (const User& user), (override));
MOCK_METHOD(void, deleteUser, (int id), (override));
};
// Пример использования с Google Mock
TEST(UserServiceTest, SavesUserCorrectly) {
MockDatabase mock_db;
// Ожидаем вызов saveUser с конкретным пользователем
EXPECT_CALL(mock_db, saveUser(_))
.Times(1) // Точно один раз
.WillOnce(Return());
// Тестируем бизнес-логику
UserService service(&mock_db);
service.registerUser("Alice", "alice@test.com");
// Проверка: был ли вызван saveUser?
};
// ============================================
// 3. FAKE — работающая реализация (но не реальная)
// ============================================
class FakeDatabase : public DatabaseInterface {
private:
std::map<int, User> in_memory_db;
public:
User getUser(int id) override {
return in_memory_db[id];
}
void saveUser(const User& user) override {
in_memory_db[user.id] = user;
}
void deleteUser(int id) override {
in_memory_db.erase(id);
}
};
// Реально работает, но хранит данные в памяти
// Быстрее реальной БД
// ============================================
// 4. SPY — обёрнутый реальный объект (записывает вызовы)
// ============================================
class SpyDatabase : public DatabaseInterface {
private:
RealDatabase real_db;
public:
std::vector<std::string> call_history;
User getUser(int id) override {
call_history.push_back("getUser(" + std::to_string(id) + ")");
return real_db.getUser(id); // Реальный вызов!
}
// ...
};
// Использует реальное поведение, но отслеживает вызовы
Практический пример Mock в C++ с Google Mock
#include <gmock/gmock.h>
#include <gtest/gtest.h>
// Интерфейс, который мы будем мокировать
class EmailService {
public:
virtual ~EmailService() = default;
virtual bool sendEmail(const std::string& to, const std::string& body) = 0;
virtual int getQueueSize() = 0;
};
// Класс, который мы тестируем (зависит от EmailService)
class UserNotifier {
private:
EmailService* email_service;
public:
explicit UserNotifier(EmailService* service) : email_service(service) {}
void notifyUser(const std::string& email, const std::string& message) {
if (!email.empty()) {
email_service->sendEmail(email, message);
}
}
};
// ============================================
// MOCK класс
// ============================================
class MockEmailService : public EmailService {
public:
MOCK_METHOD(bool, sendEmail, (const std::string&, const std::string&), (override));
MOCK_METHOD(int, getQueueSize, (), (override));
};
// ============================================
// ТЕСТ с использованием Mock
// ============================================
class UserNotifierTest : public ::testing::Test {
protected:
MockEmailService mock_email;
UserNotifier notifier{&mock_email};
};
TEST_F(UserNotifierTest, NotifiesUserWithEmail) {
// Arrange: настраиваем ожидания
EXPECT_CALL(mock_email, sendEmail("user@example.com", "Hello!"))
.Times(1) // Ровно один раз
.WillOnce(testing::Return(true));
// Act: вызываем тестируемый код
notifier.notifyUser("user@example.com", "Hello!");
// Assert: Google Mock автоматически проверит вызовы
}
TEST_F(UserNotifierTest, SkipsNotificationIfEmailEmpty) {
// sendEmail не должен быть вызван
EXPECT_CALL(mock_email, sendEmail)
.Times(0); // Никогда
// Пустой email
notifier.notifyUser("", "Hello!");
// Тест пройдёт, потому что sendEmail не был вызван
}
TEST_F(UserNotifierTest, FailureHandling) {
// Имитируем ошибку отправки
EXPECT_CALL(mock_email, sendEmail(_, _))
.WillOnce(testing::Return(false));
bool result = notifier.notifyUser("user@example.com", "Test");
// Можно проверить обработку ошибки
}
Почему Mock важен
1. Изоляция компонентов
// ❌ Без Mock — зависит от реальной БД
void badTest() {
RealDatabase db; // Медленно, требует конфигурации
UserService service(&db);
service.registerUser("Test"); // Реально записывает в БД
// Тест зависит от внешней системы!
}
// ✅ С Mock — независимый тест
void goodTest() {
MockDatabase mock_db;
EXPECT_CALL(mock_db, saveUser(_)).Times(1);
UserService service(&mock_db);
service.registerUser("Test");
// Быстро, предсказуемо, не зависит от БД
}
2. Скорость
Без Mock (реальные зависимости):
- HTTP запрос к API: 100-500 ms
- Запрос к БД: 10-50 ms
- Обращение к файловой системе: 1-10 ms
Итого: сотни миллисекунд на один тест
С Mock:
- In-memory объект: < 1 ms
Итого: тест выполняется за микросекунды
1000 тестов = 100+ минут (без Mock) vs 1 минута (с Mock)
3. Предсказуемость
// Реальная БД — непредсказуема
class PaymentService {
bool processPayment() {
if (database.isDown()) return false; // Не контролируем
if (network.isDown()) return false; // Не контролируем
return api.charge(amount); // Может быть rate-limited
}
};
// Mock позволяет тестировать все сценарии
TEST(PaymentTest, HandlesDBDown) {
MockDatabase mock;
EXPECT_CALL(mock, query(_)).WillOnce(Throw(DatabaseException()));
PaymentService service(&mock);
ASSERT_FALSE(service.processPayment());
}
4. Проверка взаимодействия
// Убедиться, что компоненты взаимодействуют правильно
TEST(AnalyticsTest, TracksUserAction) {
MockAnalytics mock_analytics;
// Проверяем не только возвращаемое значение,
// но и ВСЕ вызовы, которые сделаны
EXPECT_CALL(mock_analytics, logEvent("user_signup", _));
EXPECT_CALL(mock_analytics, logEvent("welcome_email_sent", _));
EXPECT_CALL(mock_analytics, logEvent("user_registered", _))
.Times(1);
UserService service(&mock_analytics);
service.registerNewUser("alice@example.com");
}
Инструменты для Mock в C++
-
Google Mock (gmock) — самый популярный
- Полная поддержка expectations
- Гибкая синтаксис
- Отлично интегрируется с Google Test
-
Catch2 — встроенная поддержка mock (простая)
-
trompeloeil — декларативные мокирования
-
Hippomocks — header-only, простой API
Best practices
✅ Хорошо:
- Mock только внешние зависимости (БД, API, файловая система)
- Проверять поведение, не реализацию
- Один Mock на один интерфейс
❌ Плохо:
- Мокировать всё подряд
- Чрезмерная специфичность (проверка каждого вызова)
- Комплексные Mocks, которые имитируют реальное поведение (используйте Fake)
Mock — это мощный инструмент, но как и любой инструмент, нужно использовать его правильно и с умом.