сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Тестирование API

Интеграции и тестовые данные

Интеграции и данные на стыке

Очередь доставила шесть сообщений о списаниях, уникальных среди них было три. Обработчик, который просто применял всё подряд, списал 870 рублей вместо 420. Это не сбой очереди - это её штатное поведение: она обещает доставить сообщение хотя бы раз, а не ровно раз.

Стержень: на стыке ломаются типы, время и повторы, поэтому сбои внешних систем моделируют управляемым дублёром, а результат ждут опросом, а не паузой.

// Формулировки: «как тестируешь интеграцию с внешним сервисом?», «что такое проверка туда-обратно?», «как тестировать вебхуки?».

Стыки и управляемый дублёр

Интеграционные тесты проверяют сервис вместе с настоящей базой, очередью или чужим API - то есть ровно то, чего проверка отдельных функций не видит: как данные превращаются в строки при передаче, как ведёт себя транзакция (группа изменений, которая применяется целиком или не применяется вовсе), что происходит при обрыве связи посередине.

Главный инструмент здесь - тестовый дублёр: подставной сервис, который отвечает так, как вы его научили. Он умеет то, чего не даст даже тестовая площадка партнёра: вернуть 500, ответить через десять секунд, замолчать навсегда, прислать битое тело, вернуть на четвёртый раз другое. А именно это и встретит прод - боевая среда с живыми пользователями.

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

тестовый дублёр
подставной внешний сервис с управляемыми ответами и сбоями
площадка партнёра
тестовая среда чужого сервиса; сбои в ней не закажешь

Деньги: почему нельзя дробным числом

Замеры, которые стоит один раз увидеть. Складываю по копейке тысячу раз обычным дробным числом - получаю 9.999999999999831 вместо ровно 10. Корзина из трёх товаров по 19,99 плюс 0,1 и 0,2 даёт 60.269999999999996 вместо 60,27. И классика: 0,1 плюс 0,2 равно 0.30000000000000004.

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

// Лечение - десятичный тип, который хранит число как есть: та же тысяча копеек в нём дала ровно 10.00. Как тестировщик я проверяю деньги пограничными значениями: копейка, ноль, отрицательное, очень большая сумма, деление с остатком (три человека делят сто рублей) и обязательно сверяю итог с суммой частей.

дробное число
двоичное приближение; 0,1 хранится неточно, ошибки копятся
десятичный тип
хранит копейки точно; для денег берут его

Время и типы при передаче

Второй источник тихих дефектов - часовые пояса. Замер: заказ оформлен 10 августа в 00:30 по Москве. В базе, где время хранится в UTC, это 9 августа 21:30. Отчёт «за 10 августа», построенный по UTC, этого заказа не увидит вовсе. Один и тот же момент в разных поясах: Москва 10 августа 00:30, Владивосток 07:30, Нью-Йорк 9 августа 17:30.

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

// Третье - типы теряются при передаче. Я отправил сумму десятичным типом и дату - обратно приехали строки "1234.56" и "2026-08-09 12:00:00". Формат передачи знает только числа, строки, логические значения и списки, всё остальное превращается в текст по договорённости. Отдельная ловушка - большие целые: число 9007199254740993 в браузере становится 9007199254740992, потому что там все числа дробные. Поэтому длинные идентификаторы передают строкой.

UTC
всемирное время без поясов; в нём хранят, а показывают в местном
потеря типа
сумма и дата приезжают строками; большие числа теряют точность

Асинхронность, дубли, вебхуки

Вернусь к 870 рублям из начала. Очередь гарантирует доставку «хотя бы один раз»: если подтверждение обработки потерялось, сообщение приедет снова. Значит, дубли - это норма, а не сбой, и защищаться от них должен получатель: запоминать идентификаторы обработанных сообщений и пропускать повторы. С такой защитой тот же поток списал ровно 420 рублей.

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

// Вебхуки - это когда чужой сервис сам стучится к вам с уведомлением. Их тестируют с двух сторон. На приёме: проверяется ли подпись (иначе уведомление подделает кто угодно), обрабатываются ли повторы, что будет при сообщениях не по порядку. На отправке: повторяет ли ваш сервис доставку, если получатель лежит, и не долбится ли он вечно.

доставка хотя бы раз
очередь может привезти сообщение повторно - защищается получатель
ожидание опросом
проверять результат периодически с ограничением по времени

Как отвечать: «Как тестировать интеграцию с внешним сервисом?»

Главное - не полагаться на живого партнёра и не проверять только успешные ответы. Основную массу проверок гоняю против управляемого дублёра: он умеет то, чего не даст площадка партнёра - вернуть пятисотую ошибку, ответить через десять секунд, замолчать, прислать битое тело. Именно это встретит прод, поэтому негативные сценарии строю на нём и смотрю, что мой сервис их переживает: обрывает ожидание по таймауту, повторяет только безопасные для повтора вызовы, не роняет весь сценарий из-за необязательной интеграции. Данные на стыке проверяю туда-обратно: что записал, то и прочитал. Отдельно деньги - я специально мерил, тысяча сложений по копейке обычным дробным числом дала 9,999999999999831 вместо десяти, поэтому только десятичный тип. И часовые пояса: заказ, оформленный в полпервого ночи по Москве, в базе лежит предыдущим днём по всемирному времени, и отчёт по датам его теряет. Асинхронные интеграции жду опросом с ограничением по времени, а не паузой, и обязательно проверяю дубли: очередь обещает доставить хотя бы раз, и без защиты по идентификатору сообщения обработчик списал у меня 870 рублей вместо 420.

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

На чём валят

  • Проверять только успешные ответы партнёра - прод встретит таймауты и пятисотые.
  • Деньги дробным числом: тысяча сложений по копейке дала 9,999999999999831 вместо 10.
  • Считать даты по всемирному времени: заказ в 00:30 по Москве выпадает из отчёта за этот день.
  • Не защищаться от повторов: шесть сообщений при трёх уникальных списали вдвое больше.
  • Ждать асинхронный результат паузой вместо опроса - медленно и всё равно ненадёжно.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 13, остальные разбираются в тренажёре.

  1. #integration_data1 / 5
    Как протестировать обработку таймаута или сбоя внешнего сервиса?
    A)Дождаться реального сбоя внешнего сервиса в проде и там разово проверить поведение
    B)Увеличить таймаут до нескольких минут, чтобы внешний сервис успел ответить при сбое
    C)Замокать медленный или падающий ответ и проверить фолбэк/ретрай, а не только счастливый путь с успешным сервисом
    D)Проверять успешные ответы внешнего сервиса, так как сбои — забота его команды
    показать ответ и разбор
    +C)Замокать медленный или падающий ответ и проверить фолбэк/ретрай, а не только счастливый путь с успешным сервисом

    // разбор: На реальном внешнем сервисе таймаут и сбой воспроизвести трудно и ненадёжно, а проверять их обязательно — именно тут живут баги (зависание, каскадное падение, потеря данных). Мок настраивают так, чтобы он отвечал с задержкой сверх таймаута или возвращал 500/обрыв, и смотрят, как ведёт себя система: сработал ли таймаут, есть ли ретрай с backoff, включился ли фолбэк (кэш, заглушка, деградированный ответ), не завис ли запрос навсегда, корректно ли пользователю сообщается об ошибке. Так проверяют устойчивость интеграции, а не только её счастливый путь.

  2. #integration_data2 / 5
    Что такое контракт API?
    A)Юридический договор между компанией и пользователями об условиях использования сервиса
    B)Список URL всех страниц сайта, по которому поисковики индексируют его содержимое
    C)Файл с исходным кодом серверной части API, который передают команде клиента
    D)Договорённость о формате запроса и ответа (поля, типы, коды, ошибки) между клиентом и сервером
    показать ответ и разбор
    +D)Договорённость о формате запроса и ответа (поля, типы, коды, ошибки) между клиентом и сервером

    // разбор: Контракт API — формальное соглашение о том, как стороны общаются: какие эндпоинты есть, какие поля и типы во входе и выходе, какие обязательны, какие коды и структуры ошибок возможны. Он часто описан машиночитаемо (OpenAPI/Swagger, JSON Schema, WSDL для SOAP). Контракт позволяет командам фронта и бэка работать параллельно, опираясь на согласованный интерфейс, а не на реализацию. Для тестирования он — эталон: контрактные тесты проверяют, что реальный ответ соответствует обещанному, и сигналят при несовместимом изменении, которое сломало бы клиентов.

  3. #integration_data3 / 5
    Почему нарушение контракта API опаснее, чем баг в одном значении поля?
    A)Оно ломает всех клиентов сразу и тихо (переименовали поле → у всех null)
    B)Оно опаснее, потому что для его исправления обязательно нужно перезапустить всю базу данных
    C)Оно опаснее лишь тем, что дольше воспроизводится тестировщиком при поиске дефекта
    D)Оно не опаснее: нарушение контракта затрагивает лишь одного клиента, как и баг в поле
    показать ответ и разбор
    +A)Оно ломает всех клиентов сразу и тихо (переименовали поле → у всех null)

    // разбор: Баг в конкретном значении локален: затронут один кейс, его найдут и починят. Нарушение контракта — структурное: если поле переименовали, убрали или сменили тип, ломаются все клиенты, которые на него завязаны, причём часто тихо — вместо ошибки они получают null/пропуск и продолжают работать с испорченными данными, а всплывёт это далеко от причины. Радиус поражения — вся интеграция сразу. Поэтому контракт защищают отдельно: контрактные тесты и валидация по схеме на границе сравнивают ответ с эталоном и краснеют при несовместимом изменении ещё до релиза, не давая ему дойти до потребителей.

  4. #integration_data4 / 5
    Как проверяют консистентность после цепочки запросов (создать → изменить → получить)?
    A)Достаточно проверить код ответа каждого шага; финальное состояние сверять уже не нужно
    B)После шагов запрашивают итог и сверяют его с ожидаемым
    C)Проверяют время выполнения всей цепочки, а корректность данных вторична
    D)Каждый запрос цепочки тестируют изолированно, не связывая их в сценарий
    показать ответ и разбор
    +B)После шагов запрашивают итог и сверяют его с ожидаемым

    // разбор: Многие сценарии — это последовательность связанных запросов: создать заказ, оплатить, отменить. Проверять каждый шаг по отдельности мало — важно, что итоговое состояние согласовано: после «отменить» статус реально стал «отменён», сумма к возврату верна, склад восстановлен. Тест прогоняет цепочку и финальным запросом (GET) считывает состояние, сверяя его с ожидаемым по всем связанным полям; при необходимости — и в БД. Так вскрывают баги на стыке шагов: операция вернула успех, но состояние поехало (двойное списание, застрявший промежуточный статус). Это уровень интеграционного/сценарного тестирования.

  5. #integration_data5 / 5
    Что такое состояние гонки (race condition) при параллельных запросах к API?
    A)Это ошибка, когда сервер отвечает медленнее заданного клиентом таймаута ожидания
    B)Это ситуация, когда два запроса приходят с одного IP и сервер блокирует один из них
    C)Два одновременных запроса к одному ресурсу мешают друг другу (затирают запись)
    D)Это гонка серверов за то, кто первым обработает запрос и вернёт ответ клиенту
    показать ответ и разбор
    +C)Два одновременных запроса к одному ресурсу мешают друг другу (затирают запись)

    // разбор: Когда два запроса меняют один ресурс одновременно, их порядок и переплетение операций непредсказуемы: оба читают старое значение, оба пишут своё — итог зависит от того, кто записал последним, и одно изменение теряется (lost update). Так возникают двойные списания, отрицательные остатки, дубли при параллельном создании. Ловят это специально: шлют конкурентные запросы к одному ресурсу и проверяют, что система сериализует их корректно (блокировки, транзакции, версии/optimistic locking), а не портит данные. Такие баги не видны в последовательных тестах и всплывают под реальной нагрузкой.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.