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

推荐订阅源

雷峰网
雷峰网
MyScale Blog
MyScale Blog
PCI Perspectives
PCI Perspectives
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
Lohrmann on Cybersecurity
C
Cybersecurity and Infrastructure Security Agency CISA
Spread Privacy
Spread Privacy
量子位
P
Proofpoint News Feed
T
Tailwind CSS Blog
腾讯CDC
G
Google Developers Blog
V
Visual Studio Blog
The GitHub Blog
The GitHub Blog
博客园 - 司徒正美
GbyAI
GbyAI
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
罗磊的独立博客
A
About on SuperTechFans
有赞技术团队
有赞技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
Recorded Future
Recorded Future
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
aimingoo的专栏
aimingoo的专栏
美团技术团队
Engineering at Meta
Engineering at Meta
博客园 - 【当耐特】
小众软件
小众软件
F
Fortinet All Blogs
Last Week in AI
Last Week in AI
U
Unit 42
T
The Blog of Author Tim Ferriss
B
Blog RSS Feed
云风的 BLOG
云风的 BLOG
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
Recent Announcements
Recent Announcements
S
SegmentFault 最新的问题
D
DataBreaches.Net
Stack Overflow Blog
Stack Overflow Blog
A
Arctic Wolf
C
CERT Recently Published Vulnerability Notes
Security Latest
Security Latest
L
LINUX DO - 热门话题

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Моки, стабы и фейки: в чем разница и что выбрать для автотестов?
Никита Филонов · 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 — это не спор о терминах ради терминов, а способ осознанно проектировать тесты. Когда вы понимаете, что именно хотите проверить — обработку ответа, корректность взаимодействия или работу зависимости с состоянием, — становится проще выбрать подходящий тип дублера.

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