惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
J
Java Code Geeks
L
LangChain Blog
V
V2EX
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
Jina AI
Jina AI
Blog — PlanetScale
Blog — PlanetScale
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Cloudflare Blog
博客园 - 司徒正美
B
Blog
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
MyScale Blog
MyScale Blog
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Моки, стабы и фейки: в чем разница и что выбрать для авто...
Никита Филонов · 2026-06-01 · via Все публикации подряд на Хабре

Моки, стабы и фейки: в чем разница и что выбрать для автотестов?

Средний

9 мин

7.3K

Вступление

Когда заходит разговор об автоматизации тестирования, слово «мок» (mock) становится универсальным ответом на любой вопрос. Нужно заменить базу данных? «Замокаем». Внешнее API тормозит? «Повесим мок». Сторонний сервис ещё не написан? «Ну, сделаем мок».

Проблема в том, что в 90% случаев, когда разработчик или тестировщик говорит «мок», он имеет в виду что-то другое. И это не просто вопрос душной терминологии.

Подмена понятий в инструментах тестирования приводит к тому, что тесты становятся либо слишком хрупкими и падают от любого рефакторинга, либо бесполезными, потому что проверяют не логику, а наличие вызова функции. Вы вроде бы покрыли код тестами, но при малейшем изменении внутренней структуры системы всё «краснеет», хотя бизнес-логика осталась прежней. Или наоборот: тесты «зелёные», а на проде всё падает, потому что заглушка вела себя слишком идеально.

Чтобы тесты приносили пользу, а не только увеличивали время сборки в CI/CD, нужно четко понимать, какой «дублер» нужен в конкретной ситуации.

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

Классическая терминология

Прежде чем переходить к практическим примерам, полезно зафиксировать классическую терминологию.

В литературе по тестированию Stub, Mock и Fake — это разные виды test doubles, то есть тестовых дублеров, которыми мы заменяем реальные зависимости системы.

  • Stub — это дублер, который возвращает заранее подготовленные данные. Его задача — дать системе контролируемый ответ, чтобы тест мог пройти по нужному сценарию.

  • Mock — это дублер, который не только подменяет зависимость, но и позволяет проверять взаимодействие с ней: был ли вызов, сколько раз он произошел, с какими параметрами.

  • Fake — это упрощенная, но рабочая реализация зависимости. У нее обычно есть состояние и внутренняя логика, но она не подходит для продакшена. Например, in-memory база данных, локальное файловое хранилище или упрощенная очередь сообщений.

На практике терминология часто используется менее строго. Особенно это заметно в интеграционных тестах, где вместо внутрипроцессного объекта может использоваться отдельный HTTP-сервис, который одновременно и возвращает подготовленные ответы, и сохраняет историю вызовов.

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

Стабы (Stubs): когда нужен контролируемый ответ

Стаб (stub) — это дублер, который возвращает заранее подготовленные ответы во время теста. Его задача — дать системе предсказуемый вход из внешней зависимости, чтобы мы могли проверить собственную логику в контролируемом сценарии.

Обычно стаб не используют для проверки количества вызовов или параметров обращения. Его основная роль — быть источником заранее известных данных.

Реальный пример: внешний stub-сервер на FastAPI

Представьте: вы пишете автотест для сервиса заказов. Чтобы оформить заказ, ваше приложение обращается во внешний сервис «Склад» (Warehouse) и проверяет, есть ли товар в наличии.

Настоящий сервис склада в тестовой среде может тормозить, быть временно недоступным или содержать неподходящие данные. Чтобы тест оставался стабильным и воспроизводимым, мы поднимаем свой stub-сервер на FastAPI, который всегда возвращает заранее согласованный ответ.

1. Код нашего Стаб-сервера (Warehouse Stub):

from fastapi import FastAPI

app = FastAPI()

@app.get("/inventory/{item_id}")
async def get_inventory(item_id: int):
    # Это стаб. Он не идет в реальную базу.
    # Он просто возвращает захардкоженные данные.
    return {"item_id": item_id, "status": "IN_STOCK", "quantity": 100}

2. Код автотеста (на Pytest):

import requests

def test_order_creation_when_item_in_stock():
    # В конфигурации order-service уже указан адрес stub-сервера склада:
    # WAREHOUSE_URL=http://localhost:8081

    # Act: вызываем именно нашу систему, а не сам stub
    response = requests.post(
        "http://localhost:8000/orders",
        json={"item_id": 1, "user_id": 42}
    )

    data = response.json()

    # Assert: проверяем поведение order-service,
    # который внутри сходил во внешний Warehouse Stub
    assert response.status_code == 201
    assert data["status"] == "CREATED"
    assert data["reserved_quantity"] == 1

В этом сценарии FastAPI-приложение выступает как Stub. Оно имитирует контракт реального сервиса склада, но сам тест проверяет не заглушку, а поведение order-service, который использует этот контролируемый ответ.

Это и есть ключевая идея стаба: он нужен не для того, чтобы тестировать самого себя, а для того, чтобы создать предсказуемые условия для проверки вашей системы.

Когда использовать стабы в автотестах:

  • Изоляция от внешних API. Чтобы не зависеть от сети, нестабильных тестовых сред и сторонних сервисов.

  • Контроль негативных сценариев. Когда нужно воспроизвести 404, 500, timeout или любой другой ответ, который трудно стабильно получить от реальной системы.

  • Параллельная разработка. Когда контракт уже согласован, а реальный сервис еще не готов.

  • Ускорение тестов. Стаб обычно быстрее и предсказуемее тяжелых внешних систем.

Главное отличие: Стаб vs Мок

Разница между stub и mock — не только в синтаксисе, но и в том, на чем сфокусирован тест.

  • Если вам важно, какой ответ получила система от внешней зависимости и как она его обработала, чаще всего нужен stub.

  • Если вам важно проверить само взаимодействие: был ли вызов, сколько раз он произошел, с какими параметрами, — это уже ближе к mock-подходу.

Иначе говоря: stub помогает контролировать входные данные для теста, а mock помогает проверять поведение системы по отношению к зависимости.

Моки (Mocks): когда нужно проверить взаимодействие

В классическом понимании mock — это дублер, который используется для проверки взаимодействия с зависимостью: был ли вызов, сколько раз он произошел и с какими параметрами.

На практике, особенно в интеграционных тестах, роль такого дублера может выполнять не только объект в памяти, но и отдельный HTTP-сервис, который сохраняет историю вызовов. Формально это уже ближе к mock server или test double, но по смыслу задача остается той же: проверить корректность взаимодействия.

Если stub в первую очередь поставляет контролируемые данные, то mock сфокусирован на проверке взаимодействия с зависимостью. Его задача — не только подменить зависимость, но и дать тесту возможность проверить, как именно система к ней обращалась: сколько раз, с какими параметрами и в каком контексте.

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

Реальный пример: внешний mock-сервер на FastAPI с сохранением истории вызовов

Представьте: вы тестируете отправку SMS через внешний шлюз. Вам нужно не только имитировать ответ шлюза, но и убедиться, что ваша система не отправила 10 SMS вместо одного (защита от дублей).

Для этого мы поднимаем мок-сервер, который умеет две вещи:

  1. Отдавать динамический JSON из файла.

  2. Сохранять историю всех входящих запросов в список (логировать), чтобы мы могли их проверить.

1. Код Мок-сервера (SMS Gateway Mock):

from fastapi import FastAPI, Request
import json

app = FastAPI()
# Наш "журнал посещений" — сохраняем сюда все вызовы
requests_history = []

@app.post("/send_sms")
async def send_sms_mock(request: Request):
    payload = await request.json()
    
    # Мок фиксирует факт вызова и сохраняет данные запроса
    requests_history.append(payload)
    
    # Динамически отдаем ответ из файла
    with open("sms_success_response.json") as f:
        return json.load(f)

@app.get("/_internal/history")
async def get_history():
    # Эндпоинт для нашего автотеста, чтобы проверить "шпиона"
    return requests_history

2. Код автотеста (на Pytest):

import requests

def test_sms_notification_integrity():
    # 1. Act: Запускаем бизнес-процесс (например, через API вашего приложения)
    # Ваше приложение внутри себя дергает наш Мок-сервер (localhost:8082)
    trigger_notification_api(user_id=777)

    # 2. Assert: Идем к моку и спрашиваем его "журнал"
    history_response = requests.get("http://localhost:8082/_internal/history")
    history = history_response.json()

    # Проверяем поведение системы (Behavior Verification)
    assert len(history) == 1, "SMS было отправлено больше одного раза!"
    assert history[0]["phone"] == "+79991234567"
    assert "Ваш код: 1234" in history[0]["text"]

В этом сценарии FastAPI-приложение выступает как внешний test double с возможностью проверки вызовов. Мы не просто подменили SMS-шлюз, а еще и проверили, сколько раз и с какими данными наше приложение к нему обратилось.

С практической точки зрения это mock-подход: тест проверяет не только результат, но и корректность взаимодействия с внешней системой.

Когда использовать моки в автотестах:

  • Проверка отсутствия дублей: Чтобы не списать деньги дважды или не отправить 100 пушей пользователю.

  • Сложная логика параметров: Когда важно проверить, что в Headers ушли правильные токены, а в Body — валидный JSON.

  • Гарантия вызова: Если внешняя система ничего не возвращает (fire and forget), единственный способ проверить работу — посмотреть логи в моке.

  • Динамика по ID: Мок может искать нужный файл ответа на основе id, который прислало приложение.

Главное отличие: Мок vs Стаб

Stub и mock решают разные задачи.

  • Stub нужен, когда вы хотите управлять ответом внешней зависимости и проверить, как ваша система обработает этот ответ.

  • Mock нужен, когда вам важно проверить само взаимодействие с зависимостью: был ли вызов, не было ли дублей, корректно ли сформировались параметры запроса.

Если упростить: stub отвечает на вопрос «что вернула внешняя система?», а mock — на вопрос «как именно наша система с ней взаимодействовала?».

Фейки (Fakes): почти как настоящие, но проще

Фейк (fake) — это упрощенная, но рабочая реализация зависимости. У него есть собственное состояние и логика, поэтому он ведет себя ближе к реальной системе, чем stub или mock. При этом fake не предназначен для продакшена: он создается именно как тестовая замена.

В отличие от стабов и моков, у фейка есть реальная бизнес-логика внутри. Если вы положите данные в фейковую базу данных, вы сможете их оттуда прочитать. Если вы отправите сообщение в фейковую очередь, оно там будет лежать, пока его не заберут. Но такой дублер обычно не решает продакшен-задачи вроде надежного хранения данных, масштабирования, отказоустойчивости или безопасности — он нужен именно для тестового сценария.

Реальный пример: SQLite в памяти вместо тяжелого Postgres

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

В качестве иллюстрации идеи fake можно использовать SQLite в режиме :memory:. Это рабочая база данных с собственным состоянием и SQL-операциями, которая живет только во время теста и запускается намного быстрее полноценного Postgres.

Важно помнить, что SQLite не повторяет Postgres полностью, поэтому такой подход хорошо подходит для демонстрации концепции и для части тестов, но не заменяет проверки на реальной СУБД там, где важны особенности конкретного движка.

1. Код инициализации БД (интерфейс):

import sqlite3

class OrderRepository:
    def __init__(self, db_connection):
        self.conn = db_connection

    def save_order(self, order_id, amount):
        cursor = self.conn.cursor()
        cursor.execute("INSERT INTO orders VALUES (?, ?)", (order_id, amount))
        self.conn.commit()

    def get_order_amount(self, order_id):
        cursor = self.conn.cursor()
        cursor.execute("SELECT amount FROM orders WHERE id = ?", (order_id,))
        return cursor.fetchone()[0]

2. Код автотеста с использованием Фейка:

import pytest

def test_order_storage_logic():
    # Создаем ФЕЙК — настоящую БД, но живущую только в оперативной памяти
    fake_db = sqlite3.connect(":memory:")
    fake_db.execute("CREATE TABLE orders (id INT, amount INT)")
    
    repo = OrderRepository(fake_db)

    # Act: Мы реально сохраняем данные
    repo.save_order(order_id=1, amount=500)
    
    # Assert: И мы реально их читаем обратно, проверяя логику БД
    saved_amount = repo.get_order_amount(order_id=1)
    
    assert saved_amount == 500
    # Нам не нужно было настраивать стаб на возврат 500. 
    # Система сработала "по-настоящему".

В этом сценарии SQLite — это Fake. Мы не программировали его отвечать «500» (как стаб) и не проверяли, сколько раз вызвался INSERT (как мок). Мы просто использовали упрощенную замену реальной БД.

Когда использовать фейки в автотестах:

  • In-memory реализации. Например, SQLite, H2 или другие упрощенные хранилища, когда вам нужна рабочая зависимость, но без затрат на полноценную инфраструктуру.

  • Фейковые хранилища (S3): Вместо отправки файлов в реальное облако AWS S3, используйте локальную папку или библиотеку moto, которая имитирует поведение S3 в памяти.

  • Фейковые очереди сообщений: Вместо RabbitMQ или Kafka можно использовать простую Python-очередь (queue.Queue), которая хранит сообщения в памяти во время теста.

  • In-memory кэш: Вместо реального Redis используйте обычный Python-словарь (dict), обернутый в интерфейс кэша.

Главное отличие: Фейк vs Остальные

  • Stub и mock обычно описывают заранее заданное поведение зависимости и не стремятся воспроизвести ее внутреннюю логику.

  • Fake — это упрощенная, но работающая реализация. У него есть состояние, а поведение определяется не только настройкой теста, но и собственной логикой.

На пальцах: Если вы вручную прописали ответ — это стаб. Если вы проверяете вызов — это мок. Если вы используете «легкую версию» реальной системы, которая реально работает — это фейк.

Итоговая шпаргалка: что и когда выбирать?

Чтобы не путаться в терминах, просто задайте себе вопрос: «Что я хочу проверить в этом тесте?». Ответ на него и определит нужный тип двойника.

Тип двойника

Краткая суть

Что проверяем в тесте (Assert)

Идеальный кейс

Stub (Стаб)

Возвращает заранее подготовленные ответы.

Как система обработала контролируемый ответ зависимости.

Изоляция от внешнего API, проверка обработки 404/500 ошибок.

Mock (Мок)

Дублер для проверки взаимодействия с зависимостью.

Был ли вызов, сколько раз он произошел, с какими параметрами.

Проверка отправки уведомлений, защита от лишних платных запросов.

Fake (Фейк)

Упрощенная, но рабочая реализация зависимости.

Поведение рабочей зависимости с собственным состоянием.

Упрощенная рабочая замена зависимости, например SQLite in-memory или in-memory реализация кэша.

Заключение

Разделение на stub, mock и fake — это не спор о терминах ради терминов, а способ осознанно проектировать тесты. Когда вы понимаете, что именно хотите проверить — обработку ответа, корректность взаимодействия или работу зависимости с состоянием, — становится проще выбрать подходящий тип дублера.

Это делает тесты не только быстрее, но и надежнее: они меньше зависят от случайностей среды и лучше отражают реальную цель проверки.