Тестирование OTP и SMS-верификации: гид разработчика
Содержание
- Что происходит между нажатием «Отправить код» и приходом SMS?
- Как спроектировать матрицу тестов для верификации по номеру телефона
- Как получить настоящий мобильный номер для одного тестового прогона?
- Как подключить арендованный номер к автотесту?
- Какие примеры кода нужны для Python, Node.js, Go и PHP?
- Чем тестирование отличается от сервиса к сервису?
- Как различия между странами и операторами меняют план тестирования?
- Какие сценарии отказа должен покрывать любой набор тестов для OTP
- Как отладить SMS, которое так и не пришло?
- Как нагружать OTP-сценарии тестами и не сжигать бюджет?
- Как отслеживать доставку OTP в продакшене?
- Как добиться стабильных OTP-тестов в CI?
- Как команде организовать общий доступ к номерам, ключам и бюджету?
- Какие ограничения по приватности и закону действуют при тестировании верификации?
- Частые вопросы о тестировании OTP и SMS-подтверждений
- Пройдите предстартовый чек-лист, прежде чем запускать верификацию по номеру телефона
Сквозной тест верификации по телефону это один прогон, который начинается на форме регистрации и заканчивается токеном сессии, причём SMS-этап проходит по реальному пути до реального телефона. Всё остальное (замоканный провайдер, жёстко прописанный 000000, заглушка вебхука) проверяет ваш собственный код, но не ту цепочку доставки, которая на самом деле ломается в продакшене.
Коротко: шесть шагов
- Выберите одну цель и не расширяйте охват. Тестируйте один сервис, одну страну, один маршрут оператора. Проверка «SMS в целом» даёт результаты, с которыми невозможно ничего сделать.
- Получите номер. Либо постоянная тестовая SIM-карта, которая у вас есть, либо песочница провайдера, либо арендованный номер на одну активацию.
- Запускайте активацию до отправки формы. Номер должен быть уже активен и слушать эфир в момент отправки, а не через пять секунд после.
- Отправьте номер телефона в своём приложении и зафиксируйте время запроса.
- Опрашивайте входящие SMS до тех пор, пока не придёт код или не закроется окно активации. Зафиксируйте разницу в секундах.
- Передайте код в свой эндпоинт верификации, проверьте, что сессия создана, затем закройте активацию, чтобы номер освободился.
Шаги 3 и 6 команды обычно и пропускают. Пропуск третьего даёт нестабильные тесты, которые проходят локально и падают в CI. Пропуск шестого оставляет активации открытыми, а это лишние расходы и заблокированный номер для следующего прогона.
Самые дешёвые рынки для дымовых тестов
| Страна | От | Сервисов | Номеров в наличии |
|---|---|---|---|
| США | $0.01 | 200 | 79,399,375 |
| Германия | $0.01 | 144 | 101,118,633 |
| Великобритания | $0.02 | 291 | 389,379,749 |
| Франция | $0.02 | 153 | 131,996,906 |
| Португалия | $0.02 | 143 | 140,112,396 |
| Узбекистан | $0.02 | 38 | 19,318,261 |
| Италия | $0.03 | 149 | 412,533,735 |
| Австрия | $0.04 | 126 | 183,770,041 |
Три источника тестовых номеров: сравнение
| Источник | Стоимость прогона | Время на подготовку | Реальный путь оператора | Для чего подходит |
|---|---|---|---|---|
| Физическая SIM-карта в ящике стола | Стоимость тарифа плюс человек, который смотрит в телефон | От часов до дней на каждый номер | Да | Ручные smoke-тесты, одна-две страны |
| Песочница провайдера или magic number | Бесплатно | Минуты | Нет, SMS вообще не отправляется | Юнит-тесты, прогоны собственной логики в CI |
| Арендованный номер, одна активация | От $0.04, зависит от сервиса и страны | Минуты, через API или приложение | Да | Проверка доставки, новые страны, подготовка к запуску |
Номера из песочницы подходят для тех 90 процентов вашего набора тестов, которые проверяют состояния: лимиты частоты, истечение кода, счётчики повторов, блокировку после неверного кода. Они выполняются за миллисекунды и ничего не стоят. Используйте их везде, где вы проверяете собственные ветвления логики.
Физические SIM-карты нужны там, где один и тот же номер должен жить неделями, например аккаунт, в котором вы остаётесь авторизованы как регрессионная фикстура. Цена тут человеческая: кто-то должен держать телефон в руках.
Когда реальный арендованный номер это единственный работающий вариант
Четыре случая, когда мок не может ответить на вопрос:
- Вы запускаетесь в стране, куда никогда раньше не отправляли. Нужно понять, доживёт ли ваш sender ID до маршрута в Индию или США и дойдёт ли текст целиком, без обрезки.
- Сервис фильтрует VoIP-номера. Тестовый номер должен быть настоящим мобильным номером оператора, который сервис принимает.
- Вы изменили шаблоны сообщений. Некоторые отправители переписывают или блокируют тексты со ссылками, и только реальная доставка покажет вам итоговый текст.
- Вам нужен отдельный аккаунт для QA, не привязанный к личному номеру, ради гигиены приватности, а не чтобы что-то обойти.
MarioSMS сдаёт в аренду реальные мобильные номера на одну верификацию, от $0.04, цена зависит от сервиса и страны, 35+ стран и сотни сервисов. Код появляется в панели или в приложении, обычно в течение минуты. У каждого номера есть таймер: если SMS не приходит до его истечения, активация отменяется и стоимость автоматически возвращается на баланс. Неудачная доставка не стоит вам ничего, кроме полученных данных.
Что на самом деле доказывает пройденный тест
Будьте точны в формулировках. Зелёный прогон с арендованным номером доказывает пять вещей и ни одной больше:
- Ваше приложение приняло такой формат номера для этой страны.
- Ваш отправитель отправил сообщение по этому запросу.
- Маршрут доставил текст на реальный телефон в пределах измеренного вами окна.
- Код в сообщении совпал с кодом, который сгенерировал ваш бэкенд.
- Ваш эндпоинт верификации выдал сессию по этому коду.
Он не доказывает доставку для другого оператора в той же стране, для другого sender ID или по тому же маршруту через четыре часа в пиковой нагрузке. Доставка вероятностна для каждого маршрута, поэтому один успешный прогон это одна выборка. Записывайте количество секунд на каждом прогоне, а не только «прошёл или нет». Тест, который на прошлой неделе прошёл за 8 секунд, а сегодня за 47, уже кое-что вам сообщил, ещё до того, как загорится красным, и именно этот тренд вы принесёте провайдеру, когда откроете тикет.
Что происходит между нажатием «Отправить код» и приходом SMS?
Тест верификации, который проверяет только «код пришёл», покрывает сразу пять разных систем. Когда он падает, вы не понимаете, какая из них сломалась. Если разбить пайплайн на компоненты, становится ясно, куда ставить проверки и какие таймауты относятся к какому участку.
Сколько стоит номер за один тестовый прогон
| Сервис | Самая дешёвая страна | Цена | Номеров в наличии |
|---|---|---|---|
| Telegram | Канада | $0.51 | 422,891 |
| Великобритания | $0.90 | 197,726 | |
| Узбекистан | $0.10 | 9,620 | |
| Discord | Великобритания | $0.04 | 308,494 |
| Uber | Португалия | $0.02 | 18,704 |
| Amazon | Узбекистан | $0.02 | 12,015 |
Источник: каталог MarioSMS, 2026-09-10. Цена за номер, в скобках — наличие.
Путь запроса от клиента до провайдера
Нажатие на «Отправить код» инициирует POST на ваш бэкенд, обычно /auth/phone/start с номером в формате E.164. Прежде чем появится хоть какая-то SMS, бэкенд делает четыре вещи.
Стоимость одной верификации по сервисам
| Сервис | Самая дешёвая страна | Цена |
|---|---|---|
| Telegram | Канада | $0.51 |
| Великобритания | $0.90 | |
| Узбекистан | $0.10 | |
| Discord | Великобритания | $0.04 |
| Швеция | $0.06 | |
| TikTok | Великобритания | $0.05 |
| Великобритания | $0.06 | |
| OpenAI | Великобритания | $0.06 |
- Нормализует и валидирует номер. Номер США, введённый как
(415) 555-0100, превращается в+14155550100. Некорректный ввод отсеивается здесь с ответом 400 и без затрат на SMS. - Проверяет лимиты запросов. По номеру, по IP, по отпечатку устройства, по аккаунту. У большинства команд работает как минимум три счётчика с разными окнами.
- Генерирует и сохраняет код (подробнее ниже).
- Вызывает REST API SMS-провайдера с номером, текстом сообщения и настройками отправителя, после чего возвращает клиенту 200 со значением resend-after.
Вызов провайдера, первое место, где полученный ответ не равен интересующему вас результату. Ответ 201 Created от провайдера означает, что сообщение принято в его очередь, а не что телефон что-то получил. Между «принято» и «доставлено» стоят агрегаторы, шлюзы операторов и сам телефон. Любой тест, который считает 2xx от провайдера доказательством доставки, тестирует ваш HTTP-клиент.
Как код генерируется, хешируется и хранится
Большинство реализаций генерируют от 4 до 8 цифр из криптографически стойкого источника случайности, а не через Math.random(). Запись в хранилище обычно содержит номер, хеш кода, время выпуска, срок действия, счётчик попыток и поле статуса.
Храните хеш, а не код. Верификация пересчитывает хеш из пользовательского ввода и сравнивает значения. Для тестов это важно: вы не сможете прочитать ожидаемый код из базы в интеграционном тесте, и именно поэтому команды берут реальный номер, на который приходит настоящая SMS. Арендованный номер MarioSMS доставляет код так же, как его получает пользователь, в приложении или личном кабинете, обычно в течение минуты, пока идёт таймер активации.
Срок жизни кода, продуктовое решение, чаще всего 5 или 10 минут. Счётчик попыток обычно блокирует после 3, 5 неверных вводов. И то и другое можно тестировать без единой отправленной SMS, если предусмотреть точку подмены часов.
Маршрутизация оператора, sender ID и отчёты о доставке
Приняв сообщение, провайдер выбирает маршрут. Маршрут, это путь через одного или нескольких агрегаторов до оператора-получателя, и он несёт в себе идентификатор отправителя: длинный номер, короткий номер, буквенный sender ID или зарегистрированный в 10DLC номер в США. Тип отправителя не является чистой косметикой. Некоторые страны блокируют буквенные ID, некоторые операторы понижают приоритет незарегистрированного трафика, а некоторые фильтруют сообщения, текст которых похож на маркетинговый шаблон.
Затем провайдер отправляет отчёт о доставке (DLR) вебхуком на ваш callback URL. Основные состояния:
| Состояние DLR | Что означает | Что должен делать тест |
|---|---|---|
queued | Принято, оператору ещё не передано | Продолжать ждать, не фиксировать ошибку |
sent | Передано оператору | Запустить отсчёт времени доставки |
delivered | Оператор подтвердил получение телефоном | Сверить с результатом опроса |
undelivered | Оператор отклонил или потерял сообщение | Зафиксировать код ошибки, падать явно |
failed | Сбой на стороне провайдера | Повторить по другому маршруту, поднять алерт |
DLR не всегда честны. Некоторые операторы отдают delivered уже при передаче во внутреннее хранилище, поэтому статус delivered без кода во входящих, реальный и частый случай, который заслуживает отдельного тест-кейса.
Откуда берётся задержка и как выглядит нормальное окно доставки
Задержка накапливается по всем участкам, а не возникает в одной точке.
| Участок | Типичный вклад |
|---|---|
| Клиент, ваш бэкенд | Десятки миллисекунд |
| Генерация кода и запись в хранилище | Единицы миллисекунд |
| Ваш бэкенд, API провайдера | 100, 500 мс |
| Очередь провайдера, агрегатор | Переменный, растёт под нагрузкой |
| Агрегатор, оператор | Зависит от маршрута и страны |
| Оператор, телефон | Переменный, хуже всего в часы пик |
Первые три участка ваши и практически постоянны. Всё, что идёт после вызова API провайдера, вам не подконтрольно, и именно там живёт разброс. На практике здоровый маршрут доставляет код на телефон за секунды, перегруженный может занять минуты, а сломанный не доставит никогда. Задавайте таймаут теста исходя из окна активации номера, а не наугад, и логируйте затраченные секунды на каждом прогоне, чтобы получить распределение, а не единичный случай.
Почему один и тот же код может прийти дважды или не по порядку
SMS, система store-and-forward без гарантий порядка между отдельными сообщениями. Дубли и перестановки порождают три механизма.
Повторные отправки. Агрегатор, не получивший подтверждения в своё окно, отправляет то же сообщение заново. Телефон получает два одинаковых текста, иногда с разницей в 30 секунд, иногда в 6 минут.
Повторы со стороны пользователя. Пользователь нажимает «Отправить ещё раз» до прихода первого сообщения, и в пути оказываются два валидных кода. Если ваш бэкенд аннулирует старый код при повторной отправке, то первое пришедшее сообщение уже мертво, и тест, который хватает первую попавшуюся SMS, будет падать на исправной системе.
Переключение маршрутов. Провайдер, переключающийся между агрегаторами на лету, может отправить вторую попытку по более быстрому пути, и второе сообщение обгонит первое.
В логике опроса нужно правило, какое сообщение считать победившим. Сортируйте по времени получения, берите самое свежее и фиксируйте, сколько сообщений пришло на номер за время активации. Если тест с одним запросом когда-нибудь увидит два сообщения, вы поймали цикл повторных отправок, который стоит разобрать до того, как он затронет боевой трафик.
Как спроектировать матрицу тестов для верификации по номеру телефона
Матрица тестов превращает расплывчатое «протестируйте SMS-флоу» в конечный список прогонов с заранее описанным результатом. Без неё команда двадцать раз перепроверяет один и тот же счастливый путь и выкатывает баг в повторной отправке кода. Матрица заставляет назвать все переменные, которыми вы управляете, выбрать значения для каждой и записать ожидаемый результат до того, как что-то запущено.
Каждый сервис и страна в каталоге — это ещё и вызов API
Пять измерений, которые стоит варьировать
Пять измерений покрывают большинство реальных дефектов в верификации по номеру телефона:
Как делить номера, доступ и бюджет в команде
| Вопрос | Рабочий ответ |
|---|---|
| Кто может тратить? | Один аккаунт, API-ключи по окружениям, месячный лимит |
| Кто видит коды? | Дашборд и API; держите их вне общих чатов |
| Как избежать конфликтов? | Одна активация на тестовый воркер, освобождается в teardown |
| Как проводить аудит? | История активаций по ключу, экспорт ежемесячно |
- Страна и тип номера. Виртуальный номер телефона из США ведёт себя иначе, чем индийский: отличаются и время доставки, и отображаемый sender ID. Тип номера тоже важен, потому что некоторые сервисы по-разному относятся к non-VoIP-номерам и VoIP-номерам при регистрации.
- Формат ввода. E.164 с плюсом, национальный формат с ведущим нулём, пробелы, дефисы, скобки и дважды набранный код страны. Каждый вариант должен либо нормализоваться в одно и то же значение, либо отклоняться с понятным сообщением.
- Тайминг. Код введён на 5-й секунде, на 60-й, за секунду до истечения срока и через секунду после. Баги тайминга прячутся в зазоре между заявленным сроком жизни кода и фактическим TTL токена.
- Число попыток. Первый код, повторная отправка, третья повторная отправка и попытка после того, как должен был сработать рейт-лимит.
- Корректность кода. Верный код, неверный код, просроченный код, код из предыдущей активации и код с пробелами, вставленный из уведомления.
Перемножение всех пяти измерений даёт сотни комбинаций. Все прогонять не нужно. Вы берёте покрывающий набор, где каждое значение каждого измерения встречается хотя бы один раз, и добавляете конкретные пары, которые, по вашим подозрениям, взаимодействуют между собой: например, истечение срока плюс повторная отправка.
Кейсы счастливого пути, граничные и абьюзивные
Разложите все кейсы по трём корзинам, чтобы приоритеты оставались очевидными.
Кейсы счастливого пути доказывают, что функция работает. Валидный номер, код приходит, код введён внутри окна, аккаунт создан. Здесь три-четыре кейса, по одному на каждую крупную страну, которую вы поддерживаете.
Граничные кейсы доказывают, что функция деградирует корректно. SMS не приходит вовсе, код приходит после истечения срока, пользователь меняет номер посреди сценария, приложение уходит в фон на две минуты и возвращается, сеть отваливается между отправкой и ответом. Именно здесь пользователи реально застревают.
Абьюзивные кейсы доказывают, что ваши ограничения держат нагрузку. Один и тот же номер запрошен шесть раз за минуту, один IP запрашивает пятьдесят разных номеров, код отправлен 200 раз с разными цифрами. Такое вы тестируете на собственной системе и на собственных арендованных номерах, исключительно в целях QA, в соответствии с правилами допустимого использования.
Пример таблицы матрицы с ожидаемыми результатами
| ID | Страна | Формат ввода | Тайминг | Попытка | Код | Ожидаемый результат |
|---|---|---|---|---|---|---|
| H1 | США | E.164 | 20 с | 1-я | верный | Аккаунт создан, сессия выдана |
| H2 | Индия | национальный, ведущий 0 | 30 с | 1-я | верный | Приведён к E.164, аккаунт создан |
| E1 | США | E.164 | никогда | 1-я | нет | Активация отменяется, баланс возвращён, интерфейс предлагает повтор |
| E2 | США | E.164 | срок + 5 с | 1-я | верный | Отклонено с сообщением «код истёк», предложена повторная отправка |
| E3 | Великобритания | E.164 с пробелами | 15 с | 2-я | 1-й код | Отклонено, старый код аннулирован повторной отправкой |
| E4 | Индия | E.164 | 45 с | 1-я | верный + пробел в конце | Пробел обрезан, код принят |
| A1 | США | E.164 | н/д | 6-я за 60 с | н/д | Отправка заблокирована, сообщение о задержке с остатком секунд |
| A2 | США | E.164 | 10 с | 1-я | 10 неверных попыток | Блокировка по попыткам, код сожжён, нужен новый запрос |
Каждая строка описывает один ожидаемый результат в наблюдаемых терминах. «Работает нормально» это не ожидаемый результат. «Сессия выдана, вернулся 201» это ожидаемый результат.
Сколько кейсов прогонять на релиз
Разделяйте по частоте, а не запускайте всё каждый раз.
| Триггер | Число кейсов | Нужны реальные SMS | Примерная стоимость |
|---|---|---|---|
| Каждый pull request | 3 до 5 | 0 | $0 |
| Ночная сборка | 10 до 15 | 3 до 5 | меньше $0.50 |
| Перед релизом | 25 до 35 | 8 до 12 | около $1 |
| После изменений в авторизации или у провайдера | вся матрица | 15 до 20 | несколько долларов |
При стартовой цене $0.04 за номер полный предрелизный прогон стоит дешевле чашки кофе, а неиспользованные активации возвращаются автоматически, если SMS так и не пришла.
Каким кейсам нужна реальная SMS, а какие можно замокать
Мокайте всё, что проверяет ваш собственный код в изоляции. Нормализация формата, арифметика TTL, счётчики рейт-лимита и блокировки по попыткам прогоняются на фейковом провайдере за миллисекунды. Это юнит-тесты, переодетые в строки матрицы.
Арендуйте реальный номер, когда кейс зависит от чего-то за пределами вашего процесса: фактическая доставка через оператора, отображение sender ID, разбор тела сообщения вашим регулярным выражением, поведение автозаполнения на iOS и Android, а также тайминг настоящей активации номера. Строке E1 реальный номер нужен особенно, потому что единственный честный способ проверить сценарий «SMS не приходит» это запросить номер и дать таймеру дойти до конца.
Как получить настоящий мобильный номер для одного тестового прогона?
Аренда номера под один тест это цикл из пяти шагов, который целиком занимает около двух минут. Ниже описан порядок для MarioSMS, где номер арендуется под одну верификацию за раз, а цены начинаются от $0.04 в зависимости от сервиса и страны.
Тестовая матрица, которая ловит типичные сбои
| Кейс | Настройка | Что считается пройденным |
|---|---|---|
| Штатный сценарий | Свежий номер, первая попытка | Код приходит, аккаунт создаётся |
| Медленное SMS | Опрос в течение всего окна активации | Экран ждёт, а не выдаёт ошибку на 30-й секунде |
| SMS не пришло вовсе | Дать окну истечь | Предлагается повтор, списания нет, «зависшего» аккаунта нет |
| Неверный код | Ввести четыре неверные цифры | Сообщение о блокировке, попытки считаются по номеру |
| Повторная отправка | Запросить второй код на тот же номер | Соблюдается лимит частоты, принимаются оба кода либо побеждает последний |
| Повторно используемый номер | Номер, на который уже была регистрация | Ваша обработка дублей, а не сбой |
| Несоответствие страны | Номер из другого рынка | Сообщение, которое реально нужно пользователю |
Прогоняйте каждый кейс на каждом рынке запуска, а не только у себя.
Шаг 1. Выберите сервис и страну
Откройте веб-версию на app.mariosms.com или приложение для iOS либо Android и выберите две вещи: сервис, на котором вы тестируете, и страну, к которой должен относиться номер. В наличии 35+ стран и сотни сервисов, так что большинство тестовых матриц можно закрыть с одного аккаунта.
Ограничения приватности и политики для тестовых номеров
| Правило | Что это значит на практике |
|---|---|
| Тестируйте свой продукт | Арендованные номера — для ваших флоу и ваших QA-аккаунтов |
| Никакой выдачи себя за другого | Никогда не верифицируйте аккаунт от чужого имени |
| Никакого обхода блокировок | Заблокированный аккаунт остаётся заблокированным |
| Гигиена данных | Не храните полученные коды и номера в тестовых фикстурах |
Здесь главный документ — политика допустимого использования: /acceptable-use/.
Выбирайте страну по той строке матрицы, которую вы сейчас прогоняете, а не по привычке. Номер США и номер Индии ведут себя в вашем же коде по-разному: разное число цифр, разное форматирование при отрисовке вашей библиотекой, разные sender ID в теле сообщения. Если в строке написано «IN, первая попытка, автозаполнение на Android», арендуйте индийский номер, а не американский, который просто оказался дешевле.
Цена, показанная до подтверждения, и есть та цена, которую вы платите. Запишите её в лог тестового прогона рядом с ID строки, чтобы потом на вопросы по бюджету отвечали реальные цифры, а не прикидки.
Шаг 2. Арендуйте номер и зафиксируйте окно активации
Подтвердите аренду. Номер появится с таймером рядом, это и есть окно активации. Таймер показывает, сколько времени номер остаётся вашим и продолжает ждать SMS от выбранного сервиса.
Отметьте время старта. Теперь идут двое часов, окно активации и то время жизни OTP, которое задаёт ваше собственное приложение, и большая часть непонятных результатов тестов возникает именно из-за расхождения этих двух часов. Если ваше приложение обнуляет код через 5 минут, а вы 3 минуты вручную переносите номер в форму, вы тестируете своё терпение, а не свой сценарий.
Пусть тестируемое приложение уже будет открыто на экране ввода номера до того, как вы арендуете номер. Аренда должна быть последним действием перед вставкой.
Шаг 3. Вставьте номер в тестируемое приложение
Скопируйте номер из дашборда и вставьте его в поле телефона. Здесь стоит проверить две детали, потому что они ловят настоящие баги ещё до всякой SMS:
- Принимает ли ваше поле номер в том формате, в котором его даёт дашборд, с кодом страны и без пробелов?
- Даёт ли ваша нормализация ту же строку E.164, которую увидит SMS-провайдер?
Затем нажмите «Отправить код» и запустите секундомер, либо запишите таймстемп, если прогон скриптовый.
Шаг 4. Прочитайте код в дашборде или мобильном приложении
Код появляется в дашборде или в мобильном приложении, обычно в пределах минуты. Обновите страницу или следите за строкой активации, в зависимости от того, каким клиентом пользуетесь. Для скриптовых прогонов REST API возвращает то же состояние активации, что показывает дашборд, так что тест может опрашивать его вместо человека, смотрящего в экран.
Скопируйте код в своё приложение и завершите сценарий. Фиксируйте три вещи на каждый прогон:
| Поле | Пример значения | Зачем это нужно |
|---|---|---|
| Время до кода | 14 с | База для вашего SLO по доставке |
| Полное тело сообщения | “Your code is 481920. Do not share.” | Питает ваши тесты регулярок и автозаполнения |
| Sender ID в том виде, как отображается | Короткий номер или буквенный | Меняет поведение автозаполнения на iOS |
Ваша регулярка для парсинга, ваша подсказка домена для автозаполнения на iOS и ваша справочная документация зависят от точного текста сообщения, а не только от шести цифр.
Шаг 5. Дайте таймеру истечь, если ничего не пришло
Иногда SMS не приходит. Это строка E1 из вашей матрицы, и это законный результат теста, а не сломанная подготовка теста. Не трогайте активацию и дайте таймеру дойти до нуля. Когда окно закрывается без SMS, активация отменяется, а деньги автоматически возвращаются на баланс.
Пока идёт таймер, смотрите, что делает ваше собственное приложение. Становится ли кнопка повторной отправки нажимаемой в тот интервал, который вы заложили? Крутится ли на экране спиннер спустя 90 секунд? Говорит ли пользователю хоть что-нибудь, что делать дальше? Ответы на это и есть настоящая ценность прогона.
Что возврат средств означает для бюджета на тесты
Автоматические возвраты при недоставке меняют подход к бюджету всего набора тестов. Вы платите за коды, которые пришли, а не за попытки.
Матрица из 40 строк, где 32 строки доставляются в среднем по $0.06, обойдётся примерно в $1.92, а 8 строк, специально спроектированных так, чтобы ничего не пришло, стоят $0. Считайте расходы от ожидаемого числа успешных доставок и добавляйте небольшой запас на повторы после настоящих сбоев. Пополняйте баланс картой или криптовалютой раз в спринт, а не под каждый прогон, и держитесь в рамках допустимого использования: QA и тестирование OTP на сервисах, которыми вы владеете или которые вам разрешено тестировать.
Как подключить арендованный номер к автотесту?
У теста OTP есть две половины, и работают они в разных системах. Тестовый клиент управляет приложением или API, которое вы проверяете, а отдельный клиент общается с поставщиком номеров. Тест проходит только тогда, когда обе половины сходятся: приложение приняло код, который пришёл на номер, арендованный тестом несколькими секундами ранее. Всё, о чём пойдёт речь ниже, посвящено тому, как синхронизировать эти две половины без sleep-вызовов, разбросанных по всему набору тестов.
Окно активации — это тот же таймаут, который должен соблюдать ваш тест
Цикл «запрос, опрос, извлечение, проверка»
Схема одинакова в любом языке. Пять шагов по порядку:
Чек-лист перед запуском
| Проверка | Считается выполненной, когда |
|---|---|
| Каждый рынок запуска протестирован на реальном номере | Коды приходят, аккаунты создаются |
| Протестирован путь истечения срока | Окно истекает, предлагается повтор, ничего не списано |
| Протестированы повтор и блокировка | Лимиты работают, а текст понятен |
| Протестирован резервный путь | Голосовой или email-путь работает, если он предусмотрен |
| Мониторинг работает | Доля вводов и задержка по странам на дашборде |
| Бюджет ограничен | Дневной лимит на тестовые расходы |
- Запросите активацию для нужного сервиса и страны. В ответ вы получаете ID активации и номер телефона в формате E.164.
- Отправьте этот номер в тестируемое приложение и запустите отправку кода.
- Опрашивайте статус активации, пока не появится текст сообщения или пока не истечёт таймер.
- Извлеките код из текста сообщения по шаблону, специфичному для сервиса.
- Отправьте код, проверьте полученную сессию или ошибку, затем закройте активацию.
Шаги 1, 3 и 5 это вызовы поставщика. Шаги 2 и 4 ваши. Держите их в разных модулях, чтобы смена поставщика затрагивала один файл. Рабочие примеры для опрашивающей половины есть в руководствах по Python и Node.js, тот же цикл приведён для Go и PHP.
На шаге 2 важен порядок. Сначала запросите номер, потом запускайте отправку. Если запустить отправку до того, как существует активация номера, SMS просто некуда прийти, и тест упадёт по причине, никак не связанной с вашим кодом.
Как выбрать интервал опроса и не тратить квоту впустую
На MarioSMS коды обычно приходят в течение минуты, поэтому полезное окно опроса составляет от 60 до 180 секунд в зависимости от сервиса. Интервал в 1 секунду на протяжении 120 секунд даёт 120 запросов на один тест. На 40 строк матрицы это 4 800 запросов ради обнаружения примерно 32 сообщений.
Вместо этого используйте нарастающие паузы. Рабочее расписание:
| Прошло времени | Интервал | Запросов в окне |
|---|---|---|
| 0-10 с | опроса нет | 0 |
| 10-40 с | 3 с | 10 |
| 40-120 с | 5 с | 16 |
| 120 с до конца таймера | 10 с | 6 или меньше |
Получается около 32 запросов на тест вместо 120, а в худшем случае успешный тест становится длиннее на 5 секунд. Первые 10 секунд на практике всё равно мёртвое время: сообщению нужно пройти отправителя, агрегатор и оператора, прежде чем его можно будет прочитать.
Добавляйте джиттер в 200-500 мс к каждой паузе, если тесты идут параллельно. Двадцать воркеров, опрашивающих ровно по пятисекундной сетке, дают двадцать одновременных запросов каждые 5 секунд, а для любого рейт-лимитера это выглядит как всплеск.
Как безопасно вытащить код из текста сообщения
Никогда не берите первую последовательность цифр в тексте. В реальных сообщениях встречаются и другие числа: короткие номера, годы, цифры телефона поддержки, код для отписки. Жадное совпадение \d+ в строке «Your code is 481920, valid for 10 minutes. Reply STOP to 40404» может вернуть 10 или 40404.
Опирайтесь на длину и контекст:
- Ищите группу фиксированной длины:
\b(\d{6})\bдля сервиса с 6 цифрами,\b(\d{4})\bдля сервиса с 4 цифрами. Не пишите один шаблон сразу под оба случая. - Если шаблон сообщения известен, предпочитайте выражение с ведущей фразой, а при её отсутствии откатывайтесь на простое совпадение по длине.
- Храните шаблон для каждого сервиса в той же фикстуре, где лежит длина кода, чтобы смена текста сообщения правилась одной строкой.
- Логируйте полный текст сообщения при промахе разбора. Тест, который падает с сообщением «нет совпадения» и без текста, бесполезен в два часа ночи.
- Отклоняйте совпадение, если найдены два разных кандидата нужной длины. Лучше упасть громко, чем отправить неверный код.
Бывают и буквенно-цифровые коды. Если сервис может прислать A4K9QZ, шаблону нужен символьный класс, а не \d. Форматы, с которыми вы, скорее всего, столкнётесь, описаны в статье глоссария об OTP.
Что делать, когда SMS не приходит, и как не подвесить тест
У каждой активации есть таймер. Если SMS не приходит до его истечения, активация отменяется, а деньги автоматически возвращаются на баланс, поэтому «молчаливый» тест стоит $0. Но у теста должен быть и собственный дедлайн.
Задавайте таймаут теста ниже таймера активации, а не выше. Если таймер работает 20 минут, ограничьте цикл опроса 180 секундами и выходите. Ожидание истечения таймера на стороне поставщика превращает трёхминутный прогон в двадцатиминутный.
По таймауту делайте три вещи: явно отменяйте активацию, записывайте страну, сервис и прошедшее время в секундах, и помечайте результат как no_delivery, а не failed. Отсутствие SMS это сигнал о доставке, а не сломанная проверка. Наборы тестов, которые сваливают оба случая в красное, теряют возможность заметить, что какая-то страна замолчала.
Очистка состояния между прогонами
Teardown выполняется в блоке finally, всегда, в том числе когда проверка падает прямо посреди цикла. Закрывайте или отменяйте активацию, чтобы ничего не висело открытым и не тратило баланс.
Со стороны аккаунта уборка тоже нужна. Номер, арендованный для одной верификации, одноразовый, поэтому аккаунт, созданный в первом прогоне, остаётся сиротой, когда второй прогон арендует другой номер. Либо удаляйте тестовый аккаунт через собственный API на этапе teardown, либо помечайте тестовые аккаунты идентификатором прогона и подчищайте их по расписанию. Храните номер, ID активации и ID аккаунта вместе в артефакте прогона, чтобы уборка нашла оба конца.
Никогда не кэшируйте номер для повторного использования между прогонами. Аренда покрывает одну верификацию, а устаревший номер в фикстуре порождает тест, который неделю падает не по той причине, пока кто-нибудь наконец не проверит.
Какие примеры кода нужны для Python, Node.js, Go и PHP?
Четыре языка, один контракт. Если каждый клиент в вашей организации предоставляет одни и те же три метода, тестировщик, написавший набор тестов на Python, сможет прочитать набор на Go, не открывая документацию API.
Код приходит в приложение и через API в один и тот же момент
Общий интерфейс хелпера для всех четырёх языков
Опишите интерфейс один раз, а затем реализуйте его на каждом языке. Три метода покрывают весь жизненный цикл аренды:
Одна активация: номер, окно, код, освобождение
| Метод | Вход | Возвращает | Поведение при сбое |
|---|---|---|---|
rent(service, country) | слаг сервиса, ISO-код страны | ID активации, номер телефона в формате E.164 | выбрасывает исключение, если номеров нет в наличии, списания не происходит |
wait_for_code(activation_id, timeout) | ID активации, секунды | строку с OTP | выбрасывает исключение по таймауту, активация отменяется, деньги возвращаются |
release(activation_id) | ID активации | ничего | идемпотентен, безопасно вызывать дважды |
Два правила не дают реализациям разойтись. rent никогда не возвращает номер без ID активации, потому что номер без ID невозможно освободить. wait_for_code опрашивает сервер с дедлайном, который задаёт вызывающая сторона, а не с константой, зашитой внутрь хелпера, так что медленный индийский маршрут и быстрый американский могут использовать один и тот же код с разными лимитами времени.
Логика возврата средств определяет обработку ошибок. Активация номера, на который за отведённое окно не пришло SMS, отменяется сама, и цена возвращается на баланс, поэтому таймаут в wait_for_code не стоит ничего. А значит, агрессивные таймауты обходятся дёшево. Ставьте 90 секунд, а не 10 минут.
Python с requests и фикстурами pytest
Используйте requests.Session с примонтированным HTTPAdapter, чтобы переиспользование соединений и политика повторов жили в одном месте. Оберните хелпер в pytest-фикстуру с областью видимости функции, которая отдаёт номер через yield и освобождает его в блоке teardown, тогда даже упавшая проверка вернёт активацию.
@pytest.fixture
def rented_number(sms_client):
act = sms_client.rent(service="telegram", country="us")
try:
yield act
finally:
sms_client.release(act.id)
Опрашивайте через time.monotonic(), а не time.time(), иначе перевод часов посреди прогона растянет или обрежет окно ожидания. Полные форматы запросов и ответов есть в руководстве по Python.
Node.js с fetch и Playwright
В Node 18 и новее уже есть fetch и AbortSignal.timeout(), так что HTTP-библиотека не нужна. Передавайте signal: AbortSignal.timeout(10_000) в каждый запрос опроса, чтобы зависший сокет отваливался за 10 секунд, а не висел до таймаута всего набора тестов.
В Playwright размещайте аренду в фикстуре с областью видимости воркера или теста и регистрируйте очистку в teardown этой фикстуры, а не в test.afterEach. Фикстуры Playwright разрушаются в обратном порядке, поэтому аренда, созданная до контекста браузера, освобождается уже после закрытия контекста, а именно это вам и нужно, пока приложение ещё держит сессию. В руководстве по Node.js показан цикл опроса на промисах с setTimeout вместо busy-цикла while.
Go с дедлайнами контекста
Go делает контракт по таймаутам явным. Каждый метод хелпера принимает ctx context.Context первым аргументом и передаёт его в http.NewRequestWithContext. Лимит задаёт вызывающая сторона:
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
code, err := client.WaitForCode(ctx, act.ID)
Для интервала опроса используйте time.NewTicker(3 * time.Second) и select сразу по ticker.C и ctx.Done(). Освобождайте номер со свежим контекстом внутри defer, потому что к моменту выполнения отложенного вызова родительский контекст уже мёртв. Именно эта ошибка вызывает молчаливые сбои освобождения в большинстве первых версий кода.
PHP с Guzzle и PHPUnit
Стек обработчиков Guzzle принимает middleware, поэтому логику повторов стоит положить туда один раз, а не дублировать в каждом месте вызова. Задавайте timeout и connect_timeout в конфигурации клиента по отдельности, ведь зависшему DNS и медленному ответу нужны разные лимиты.
В PHPUnit не используйте setUp для аренды. Арендуйте номер внутри самого теста и освобождайте его в блоке finally, либо возьмите data provider, который подставляет коды стран, чтобы одно тело теста покрывало Соединённые Штаты и другие рынки без дублирования.
Повторы, бэкофф и типизация ошибок в каждом языке
Три класса ошибок, и повторять запрос имеет смысл только при одном из них:
- Временные (HTTP 429, 502, 503, обрыв соединения). Повторяйте с экспоненциальным бэкоффом, начиная с 1 секунды, с потолком в 8 секунд и максимум 4 попытками.
- Окончательные (нет номеров для этого сервиса и страны, недостаточно средств, неверный API-ключ). Падайте сразу и выводите сервис и страну в тексте ошибки.
- Пустой опрос (SMS ещё не пришло). Это не ошибка. Продолжайте опрос до дедлайна, заданного вызывающей стороной.
Типичный баг: пустой опрос принимают за временную ошибку и скармливают его циклу бэкоффа. Бэкофф растягивает интервал опроса до 8 секунд, и код, пришедший на 41-й секунде, считывается на 48-й, когда проверка уже отработала. Опрашивайте с фиксированной частотой раз в 3 секунды, а бэкофф оставьте только для HTTP-сбоев.
Чем тестирование отличается от сервиса к сервису?
Если набор тестов проходит на одном сервисе и падает на четырёх других, внутри обычно зашито жёсткое допущение: код состоит из шести цифр, SMS приходит за 20 секунд, текст сообщения умещается в одну строку. Каждое из них где-то верно и где-то неверно. Ниже собраны варианты поведения, которые ломают наивные тесты, сгруппированные по категориям.
Рынки с наибольшим количеством номеров в наличии для нагрузочного тестирования
| Страна | Номеров в наличии | Сервисов | От |
|---|---|---|---|
| Великобритания | 37,001,905 | 189 | $0.02 |
| Италия | 24,353,864 | 118 | $0.03 |
| Австрия | 12,315,289 | 105 | $0.04 |
| Канада | 11,766,481 | 37 | $0.05 |
| Индонезия | 8,380,976 | 45 | $0.04 |
| США | 2,954,215 | 139 | $0.01 |
| Австралия | 2,255,940 | 109 | $0.04 |
| Германия | 2,147,090 | 123 | $0.01 |
Источник: каталог MarioSMS, 2026-09-10. Цена за номер, в скобках — наличие.
Мессенджеры и их форматы кодов внутри приложения
Мессенджеры отправляют код по SMS при первой регистрации, но многие из них дублируют тот же код по собственному внутреннему каналу, если аккаунт уже существует на другом устройстве. У тестового аккаунта такого устройства не будет, поэтому рассчитывайте только на путь через SMS и закладывайте задержку, после которой приложение к нему вернётся.
Выберите рынок для теста — в строке видно наличие и цену
Здесь возникают две проблемы с разбором. Во-первых, код часто стоит внутри длинного предложения с предупреждением о безопасности в конце, поэтому наивное регулярное выражение вида «первая цепочка цифр в тексте» может поймать телефон поддержки вместо кода. Привязывайте регулярное выражение к метке, которую использует конкретный отправитель. Во-вторых, некоторые мессенджеры добавляют скрытый суффикс app-hash для Android SMS Retriever, а это ещё 11 символов после кода. Отрезайте их перед сравнением.
Повторная отправка в мессенджерах обычно блокируется на минуту и дольше после первой попытки, причём ограничение действует на номер телефона, а не на сессию. Цикл повторов, который трижды нажимает Resend, просто продлевает собственное ожидание.
Регистрации в соцсетях и на маркетплейсах
В соцсетях и на маркетплейсах проверка телефона обычно спрятана за шагом с email, капчей или анкетой профиля, то есть SMS приходит далеко не первым действием теста. Закладывайте время на подготовительные шаги отдельно от проверки SMS-верификации, иначе таймаут, выставленный на весь сценарий, истечёт ещё до запроса кода.
У маркетплейсов есть дополнительная тонкость. Многие из них просят подтверждение повторно при первом объявлении или первом сообщении, а не при регистрации. Если тест покрывает только регистрацию, вторая проверка остаётся непротестированной. Оформляйте её отдельным тестом с собственным номером, а не переиспользуйте номер регистрации: активация из первого прогона к этому моменту уже закрыта.
Такси и доставка с коротким окном
У сервисов такси и доставки самый жёсткий срок жизни кода среди распространённых категорий, часто 60 до 120 секунд, и некоторые из них отправляют код автоматически, как только поле заполнено. Такое сочетание наказывает медленный опрос. Если ваш поллер спит 5 секунд между проверками, а приложение аннулирует код через 60, вы сожгли 8 % окна на одну паузу.
Автоотправка также ломает классический паттерн Selenium «ввести код в поле, затем нажать Submit», потому что клик приходится уже после перехода на другой экран. Введите код, а затем ждите либо элемент успеха, либо кнопку Submit, что появится раньше.
Финансовые сервисы и кошельки с более строгими проверками
Финансовые приложения и кошельки проверяют номера строже всех. Они склонны отклонять известные VoIP-номера, требовать совпадения страны номера со страной аккаунта и блокировать аккаунт после трёх неверных кодов, а не после пяти. Некоторые ещё и привязывают код к отпечатку устройства, поэтому код, полученный на одной машине и введённый на другой, падает с общей ошибкой, похожей на «неверный код».
Тестируйте такие сервисы с не-VoIP номером из той же страны, что и профиль аккаунта, и относитесь к блокировке как к полноценному ожидаемому исходу с отдельным тестом, а не как к случайности, которую вы обнаруживаете в CI.
Сервисы, предпочитающие звонок вместо SMS
Некоторые сервисы переключаются на голосовой звонок после одной или двух неудачных попыток по SMS, а часть из них для отдельных стран изначально работает через звонок. Тест, который читает только SMS, видит пустой ящик и сообщает о сбое доставки, хотя на самом деле сервис сменил канал.
Действуйте двумя способами. Определяйте переключение по интерфейсу (текст кнопки меняется на «Call me») и проверяйте эту ветку как допустимую. А если известно, что в конкретной стране сервис предпочитает звонок, уберите эту комбинацию из матрицы SMS вместо того, чтобы каждую ночь получать падение. В MarioSMS, если SMS не приходит в течение окна активации, активация отменяется и стоимость автоматически возвращается на баланс, так что сервис с приоритетом звонка стоит вам времени, а не денег.
Сравнительная таблица длины кода и окна
Длина кода, срок действия и правила повторной отправки различаются от сервиса к сервису и меняются со временем. Измеряйте их в собственном окружении, а не доверяйте статичному списку, и фиксируйте результаты в таблице вроде этой.
| Категория | Типичная длина кода | Срок на подтверждение | Пауза перед повтором | Заметка для теста |
|---|---|---|---|---|
| Мессенджеры | 6 цифр | Минуты | 60 с и больше | Отрезать суффикс app-hash |
| Соцсети | 5 до 6 цифр | Минуты | 30 до 60 с | Капча перед отправкой |
| Маркетплейсы | 4 до 6 цифр | Минуты | 60 с | Вторая проверка при первом объявлении |
| Такси и доставка | 4 до 6 цифр | 60 до 120 с | 30 с | Автоотправка при заполнении |
| Финансы и кошельки | 6 до 8 цифр | Короткий | 60 с и больше | Совпадение страны, блокировка после 3 попыток |
| Инструменты для разработчиков | 6 цифр | Минуты | 30 с | Часто TOTP наряду с SMS |
Заполняйте колонку срока измеренными секундами из собственных прогонов. Двух чисел в строке достаточно, чтобы рассчитать дедлайн опроса: минимальный наблюдаемый срок жизни кода и максимальное наблюдаемое время доставки. Если разрыв между ними меньше 10 секунд, этому сервису нужна более частая частота опроса, чем остальным тестам в наборе.
Как различия между странами и операторами меняют план тестирования?
Флоу верификации это не одна система. Это ваш код, провайдер рассылок, международный маршрут, оператор на стороне получателя и локаль самого телефона, и четыре из этих пяти элементов меняются, стоит вам поменять код страны. Сборка, которая доставляет код на американский номер за 4 секунды, может уйти в таймаут на 90 секундах у того же провайдера на индийском номере, причем без единой ошибки на вашей стороне.
Форматы номеров, E.164 и особенности местного набора
Храните каждый номер в E.164 (плюс, код страны, абонентский номер, без пробелов и дефисов) и форматируйте только на уровне отображения. Сбои возникают из разрыва между тем, что вводит пользователь, и тем, что требует E.164.
Один баланс, одно место, чтобы ограничить траты тестового набора
- Ведущие нули междугородного префикса. Пользователи из Великобритании, Германии и Италии вводят
07911 123456. E.164 для Великобритании требует+447911123456, но итальянские мобильные номера сохраняют первую цифру, поэтому универсальное правило «срезать ноль» их портит. - Переменная длина национального номера. В некоторых странах мобильные номера содержат от 8 до 11 цифр в зависимости от выделенного оператору диапазона, так что жесткая проверка
length === 10отбраковывает валидные номера. - Совпадающие коды стран. +1 покрывает США, Канаду и примерно 20 карибских территорий. Правило «только США», написанное как
startsWith('+1'), пропускает их все. - Артефакты вставки. Неразрывные пробелы, короткие тире и юникодные цифры прилетают из менеджеров паролей и списков контактов. Нормализуйте до валидации.
Проверяйте каждый арендованный номер в трех форматах ввода: полный E.164, национальный формат с корректно выбранной страной и национальный формат с неверно выбранной страной. Третий случай должен приводить к понятному сообщению об ошибке, а не к 500.
Правила по Sender ID и буквенно-цифровые отправители
Поле From регулируется по-своему на каждом рынке, а ваши проверки, скорее всего, от него зависят.
| Модель рынка | Что приходит | Влияние на тесты |
|---|---|---|
| Буквенно-цифровые разрешены без регистрации | Название вашего бренда | Тесты с ответом STOP бессмысленны, отправитель односторонний |
| Буквенно-цифровые с предварительной регистрацией | Зарегистрированный ID, либо сообщение отбрасывается | Незарегистрированный ID на стенде падает молча |
| Буквенно-цифровые запрещены | Длинный или короткий цифровой номер | Любая проверка текста отправителя ломается |
| Динамический выбор маршрута | Каждая отправка с другого номера | Никогда не сверяйте точное значение отправителя |
Вытаскивайте код из тела сообщения, а не из отправителя. Пишите регулярку по тексту сообщения с ограниченным шаблоном (\b\d{6}\b), а отправителя проверяйте только на «присутствует и не пуст».
Фильтрация у операторов и контент, который блокируют
Операторы на стороне получателя прогоняют содержимое сообщений через спам-фильтры, и на одних рынках эти фильтры строже, чем на других. Фильтрацию обычно провоцируют укороченные ссылки, слова капсом, символы валют, больше одной ссылки и незарегистрированные идентификаторы отправителя. Итог, как правило, тихое отбрасывание: провайдер рапортует о приеме, а на телефон ничего не приходит.
Сделайте один тест, который отправляет ваш точный продакшен-шаблон, и второй, который отправляет намеренно «фильтроопасный» вариант. Если в стране A доходят оба, а в стране B только первый, вы нашли проблему шаблона, а не инфраструктуры. Базовые требования к виду сообщения смотрите в /glossary/sms-verification/.
Как выбрать пять стран, которые закрывают большую часть рисков
Пяти достаточно, если подбирать их по разным осям, а не по размеру рынка.
- Ваша крупнейшая пользовательская база, какая бы страна это ни была.
- Одна страна с 10-значным национальным форматом и жесткой фильтрацией у операторов, например Индия (/numbers/india/).
- Одна страна с кодом +1, кроме США, чтобы ловить баги вида
startsWith('+1'). - Одна страна ЕС с ведущим нулем префикса, чтобы прогонять ваш нормализатор.
- Одна страна, куда провайдер отправляет по редко используемому маршруту, чтобы медленная доставка всплыла раньше, чем о ней сообщит пользователь.
США (/numbers/united-states/) обычно попадают в первый или третий пункт. Прогоняйте весь набор перед каждым релизом, а крупнейшую страну на каждом мерже.
Цены и наличие как входные данные для планирования
Цены MarioSMS начинаются от $0.04 за номер и зависят от сервиса и страны, в наличии 35+ стран и сотни сервисов. Отсюда два следствия для планирования. Во-первых, ранжирование по цене показывает, какие страны можно молотить объемом, а какие лучше выборочно: ставьте высокочастотную регрессию на дешевые направления, а матрицу из пяти стран на фиксированную еженедельную периодичность. Во-вторых, наличие считается по паре сервис плюс страна, поэтому набор тестов с зашитой страной падает, когда эта комбинация закончилась. Читайте наличие в момент запуска и переключайтесь на следующую страну из списка.
Каждый номер рассчитан на одну верификацию, у него есть таймер, и если SMS не приходит, активация отменяется, а деньги автоматически возвращаются на баланс. Неудачные тесты доставки поэтому ничего не стоят, что делает негативное тестирование на медленных рынках доступным.
Влияние часовых поясов на доставку и на расписание тестов
Очереди у операторов наиболее загружены в дневные часы по местному времени, а часть рынков в пиковые часы по-разному ограничивает рекламный и транзакционный трафик. Ночной прогон в 09:00 UTC попадает в Индию в середине дня, а на западное побережье США в 02:00, так что ваши цифры по задержкам описывают два разных состояния сети и усредняются в нечто бессмысленное.
Записывайте местный час в стране получателя рядом с каждым замером. Затем ставьте по два прогона на страну, один в 10:00 и один в 22:00 по местному времени, и сравнивайте p95 времени доставки между ними. Разрыв больше 15 секунд означает, что продакшен-таймаут нужно рассчитывать по часу пик, а не по спокойному часу.
Какие сценарии отказа должен покрывать любой набор тестов для OTP
В процессе верификации всего около шести подвижных частей, и каждая из них ломается так, что снаружи всё выглядит нормально. Вход выполнен, дашборд загрузился, тест зелёный. Отказы ниже переживают набор тестов, который проверяет только счастливый путь, поэтому под каждый нужен тест, написанный против сбоя, а не против успеха.
Бэкенд принимает просроченный код
Большинство команд прописывают TTL (обычно 5 или 10 минут) в конфиге и никогда не проверяют, что проверка вообще срабатывает. Код попадает в таблицу со столбцом expires_at, и если запрос фильтрует по code = ? и user_id = ?, но забывает про метку времени, старый код работает вечно.
Напишите тест, который запрашивает код, замораживает или переводит часы за пределы TTL, а затем отправляет код. Проверьте, что вернулся HTTP 400 или 401 и тело ошибки code_expired. Также проверьте, что строка либо удалена, либо помечена, чтобы и второй вызов не прошёл. Если ваш фреймворк не умеет управлять временем на сервере, вставьте строку с кодом напрямую, задав expires_at в прошлом, и отправьте её через публичный эндпоинт.
Повторное использование кода после успешного входа
Код должен быть одноразовым. Типичный баг: обработчик верификации возвращает сессию, но не помечает код как использованный, и те же шесть цифр срабатывают трижды за следующие четыре минуты. Это важно, когда SMS остаётся в истории уведомлений или в общем ящике.
Проверяйте дважды в одном тесте. Первый вызов возвращает 200 и токен сессии. Второй вызов с теми же данными возвращает 400 и не возвращает токен. Затем убедитесь, что второй вызов не создал вторую строку сессии, потому что некоторые реализации отклоняют код, но всё равно выдают токен из кэшированного пути.
Перебор без ограничения попыток
Шесть цифр это миллион комбинаций, что звучит безопасно, пока не посчитаешь, сколько нужно атакующему на самом деле. При окне в 10 минут и отсутствии лимита скрипт со скоростью 50 запросов в секунду покрывает всё пространство задолго до истечения срока. Решение: счётчик попыток на код, обычно от 3 до 5, плюс ограничение частоты запросов на номер.
Прогоните в тесте цикл с неверными кодами до настроенного лимита, затем отправьте правильный код и убедитесь, что он отклонён. Блокировка после 5 неверных попыток бесполезна, если правильный код всё ещё срабатывает на шестой.
Гонка между повторной отправкой и верификацией
Пользователь нажимает «отправить повторно» на 29-й секунде, первое SMS приходит на 30-й, и он вводит первый код. Сработает это или нет, зависит от того, аннулирует ли повторная отправка старый код или оба остаются активными. Обе политики защитимы, но тест должен зафиксировать, какую именно вы выбрали.
Отправьте запрос на повторную отправку и запрос на верификацию первого кода одновременно из двух потоков. Проверьте ровно один исход. Если старые коды остаются валидными, убедитесь, что оба проходят каждый со своим кодом и что третий код не был выпущен. Если повторная отправка аннулирует старый код, убедитесь, что первый код возвращает code_superseded. Смежная тема: окна активации номера на арендованном номере заканчиваются независимо от TTL вашего приложения, поэтому тест, который длится достаточно долго, чтобы задеть оба, должен различать эти два типа отказа.
Перечисление номеров через разные сообщения об ошибках
Регистрация возвращает «номер уже используется», а вход возвращает «аккаунт не найден». Две разные строки, и теперь любой со списком номеров может рассортировать их на клиентов и не клиентов. Тайминги ответов выдают ровно то же самое, когда путь для зарегистрированного номера считает хеш пароля, а путь для незарегистрированного возвращается мгновенно.
Проверьте, что коды статуса совпадают, а тела ответов побайтово идентичны для зарегистрированного и незарегистрированного номера. Для таймингов прогоните по 50 запросов на каждую ветку и убедитесь, что медианная разница остаётся ниже 50 мс. Для незарегистрированной стороны используйте свежий арендованный номер, чтобы состояние было действительно чистым, и проверьте Соединённые Штаты и одну страну не из США, поскольку некоторые бэкенды ветвятся по коду страны.
Допущения о подмене SIM и переиспользовании номеров
Операторы переиспользуют номера после периода отключения, а подмена SIM переносит номер на новое устройство за минуты. Если ваше приложение считает номер телефона постоянной личностью, оба события отдают аккаунт постороннему.
Тестируйте путь восстановления, а не путь регистрации. Убедитесь, что подтверждённого номера самого по себе недостаточно для сброса пароля без второго фактора, и что повторная верификация на существующем аккаунте пишет строку в аудит. Убедитесь, что смена номера в профиле аннулирует активные сессии и отправляет уведомление на старый контакт.
Тихий сбой провайдера с ответом 200
SMS-шлюз принимает сообщение, возвращает 200 с идентификатором сообщения и теряет его на стороне оператора. Ваш эндпоинт отправки рапортует об успехе, а тест проходит, потому что проверял только ответ API. Разрыв всплывает в виде тикетов в поддержку.
Никогда не проверяйте только ответ на отправку. Проверяйте код, реально прочитанный из настоящего входящего ящика, а это как раз то, что даёт арендованный номер. Затем убедитесь, что ваш обработчик отчётов о доставке фиксирует финальный статус в течение 120 секунд и что отсутствие отчёта вызывает ошибку, а не записывается как «доставлено».
Таблица «сценарий отказа - проверка»
| Сценарий отказа | Триггер в тесте | Проверка, которая его ловит |
|---|---|---|
| Принят просроченный код | Перевести часы за пределы TTL | 400 code_expired, строка использована |
| Повторное использование кода | Верифицировать дважды одним кодом | Второй вызов 400, второй сессии нет |
| Нет лимита попыток | N неверных кодов, затем верный | Верный код отклонён после лимита |
| Гонка при повторной отправке | Одновременные повторная отправка и верификация | Ровно один задокументированный исход |
| Перечисление номеров | Зарегистрированный против незарегистрированного номера | Идентичное тело, медианная разница ниже 50 мс |
| Подмена SIM | Повторная верификация на существующем аккаунте | Записана строка аудита, сессии аннулированы |
| Тихий сбой провайдера | Отправить, затем прочитать настоящий ящик | Код прочитан из SMS, отчёт в пределах 120 с |
Каждая строка стоит один арендованный номер от $0.04, а активация, которая ничего не получила, возвращает деньги автоматически, так что набор из всех семи тестов укладывается меньше чем в доллар за полный прогон.
Как отладить SMS, которое так и не пришло?
У пропавшего кода есть пять правдоподобных причин, и находятся они на разных участках маршрута: клиент вообще не отправил запрос, провайдер отклонил сообщение, оператор его потерял, диапазон номеров получателя плохой или сам номер мертв. Разбирайтесь по порядку и на каждом участке собирайте хотя бы одно доказательство, прежде чем идти дальше. Гадать выйдет дороже, чем проверить.
Шаг 1: убедитесь, что запрос ушел от клиента
Прежде чем винить всё, что ниже по цепочке, докажите, что приложение вообще сделало вызов. Откройте лог сети для тестового прогона и найдите POST к вашему эндпоинту отправки. Вам нужны три факта: запрос ушел, в нем был номер телефона в том формате, который вы ожидали, и вернулся код 2xx.
Типичные проблемы здесь скучные. Правило валидации на стороне клиента молча съело отправку формы. Номер хранился как 07700 900123 и ушел без кода страны. Защита от повторной отправки из прошлого теста всё еще держала кнопку заблокированной 60 секунд. Просроченный токен авторизации превратил отправку в 401, который интерфейс показал как обычный спиннер.
Что зафиксировать:時 время запроса в UTC, точную строку E.164, которую отправили, HTTP-статус, тело ответа и номер сборки клиента.
Шаг 2: проверьте ответ провайдера и ID сообщения
Код 2xx от вашего собственного бэкенда не означает, что провайдер принял сообщение. Посмотрите тело ответа провайдера на вашу отправку. Значение имеют два исхода.
Если вернулся ID сообщения, значит провайдер поставил его в очередь. Запишите его. Этот ID, единственный ключ, который связывает ваши логи с логами оператора, и без него разговоры с поддержкой ни к чему не приведут.
Если вместо этого вернулась ошибка, читайте код, а не человекочитаемый текст. У провайдеров разные коды для немаршрутизируемого префикса, заблокированной страны назначения, незарегистрированного для этой страны sender ID, нехватки средств и превышения лимита частоты. Каждый случай лечится по-своему, а текст сообщения нередко одинаковый сразу у трех из них.
Шаг 3: прочитайте статус отчета о доставке
Отчет о доставке (DLR), это ответ оператора. Статусы обычно определяются за 5, 30 секунд, и отчет, который спустя 120 секунд всё еще висит в queued, сам по себе является находкой.
| Статус | Что означает | Что делать дальше |
|---|---|---|
queued / accepted | Сообщение у провайдера, оператор не подтвердил | Подождать 120 с, затем считать проблемой маршрута |
sent | Передано оператору, подтверждения нет | Проверить, возвращает ли этот маршрут отчеты вообще |
delivered | Оператор подтвердил доставку на телефон | Проблема в чтении или во входящих, а не в отправке |
undelivered | Оператор принял, а потом потерял | Фильтрация, sender ID или содержимое |
failed | Отклонено сразу | Плохой номер, закрытый диапазон или мертвая SIM |
Статус delivered при том, что кода нигде не видно, означает, что сломан ваш цикл опроса, парсер или временное окно, а не SMS-цепочка. Сначала сами прочитайте сырое тело входящего сообщения, а уже потом заводите заявку по доставке.
Шаг 4: проверьте вторую страну или второй сервис
Один сбойный маршрут ничего не говорит о масштабе проблемы. Отправьте ту же полезную нагрузку на второй номер назначения и сравните. Если номер США получает сообщение за 20 секунд, а Индия уходит в таймаут, то у вас проблема с маршрутом или фильтрацией на этом направлении, а не сломанный шаблон. Если не работает и там, и там, проблема выше по цепочке, в вашем коде или в аккаунте у провайдера.
Сделайте такое же разделение по сервисам. Если код от вашего собственного приложения приходит, а от стороннего сервиса нет, значит сервис фильтрует или ограничивает отправку, и чинить это нужно на его стороне.
Шаг 5: смените номер и повторите один раз
Отдельные номера портятся. SIM-карту переоформили, диапазон заблокировал конкретный отправитель, оператор режет A2P-трафик на этот блок. Арендуйте свежий номер и повторите ровно ту же отправку один раз.
В MarioSMS каждый номер арендуется под одну верификацию за раз, от $0.04, и если SMS не приходит в течение окна активации, активация отменяется, а деньги автоматически возвращаются на баланс. Так что повтор, который тоже не сработал, не стоит вам ничего. Два подряд сбоя на двух разных номерах, веский довод в пользу проблемы с маршрутом или сервисом. Один сбой и затем успех означают, что виноват был первый номер: запишите это и идите дальше.
Повторяйте один раз, а не пять. Многократные отправки на один и тот же номер в пределах минуты срабатывают на антифлуд-правила и превращают чистый сигнал в шум.
Поля логов, которые ускорят разбор следующего инцидента
Логируйте это в момент отправки, а не после инцидента:
request_idот вашего бэкенда, проброшенный до клиента.provider_message_idровно в том виде, в каком он вернулся.msisdn_e164, хешированный или замаскированный до последних 4 цифр.country_iso2иservice_key.sent_at_utcс точностью до миллисекунд.dlr_statusвместе сdlr_received_at_utc, обновляемые на месте.activation_idот провайдера аренды, чтобы номер и отправка сходились в одной строке.attempt_numberи ID предыдущей попытки, которую эта заменяет.
С этими восемью полями типичный инцидент с SMS решается чтением одной строки вместо сопоставления четырех систем по временным меткам. Примеры на Python и Node.js оба возвращают ID активации при аренде, так что поле 7 обойдется вам в одно присваивание переменной.
Как нагружать OTP-сценарии тестами и не сжигать бюджет?
Нагрузочный тест, который арендует реальный номер под каждого виртуального пользователя, превращается в счёт за услуги, а не в тест. При цене $0.04 за номер 5 000 смоделированных регистраций обходятся в $200 за прогон, а если набор тестов запускается на каждый мерж в main, вы платите эту сумму четыре раза в день. Решение состоит в том, чтобы тестировать две разные вещи по отдельности, потому что ломаются они по разным причинам.
Этапы, через которые проходит OTP
| Этап | Кто отвечает | Типичное время | Что здесь ломается |
|---|---|---|---|
| Запрос принят | Ваш бэкенд | Менее 200 мс | Лимиты частоты, валидация, повторные отправки |
| Сообщение поставлено в очередь | Ваш SMS-провайдер | Менее 1 с | Выбор маршрута, отклонённый sender ID |
| Принято оператором | Оператор связи | 1–10 с | Фильтрация, заблокированный отправитель, «серый» маршрут |
| Доставлено на телефон | Сеть | От 2 с до нескольких минут | Роуминг, перегрузка сети, выключенное устройство |
| Код введён | Пользователь | От секунд до минут | Истечение срока, повторные попытки, автозаполнение |
Ваши логи обычно останавливаются на втором этапе — поэтому тесты на реальных номерах находят то, что упускает мониторинг.
Разделите нагрузочный тест и тест доставки
Тест пропускной способности отвечает на вопрос, выдержит ли ваш API 500 запросов на отправку в секунду: пулы соединений, конкуренция за запись в базу, глубина очереди, сам рейт-лимитер. Тест доставки отвечает на вопрос, дойдёт ли код до реального телефона в Индии и совпадёт ли он. Первому нужен объём и не нужны операторы. Второму нужны реальные номера и почти не нужен объём.
| Тип теста | Объём за прогон | Реальные номера | Что определяет стоимость | Когда запускается |
|---|---|---|---|---|
| Пропускная способность | от 1 000 до 100 000 запросов | Нет | Только вычисления | Каждый мерж |
| Лимиты частоты | от 50 до 500 запросов | Нет | Только вычисления | Каждый мерж |
| Выборка доставки | от 5 до 30 активаций | Да | Цена за активацию | Ночью |
| Гейт перед релизом | от 20 до 60 активаций | Да | Цена за активацию | Перед выкаткой |
Мок провайдера на границе
Ставьте шов на уровне вашего собственного интерфейса SMS-клиента, а не на уровне HTTP-библиотеки. Один интерфейс с методами send(to, body) и fetchCode(activationId) даёт вам фейк, который возвращает фиксированный код за 0 мс, и реальную реализацию, которая ходит к провайдеру. Под нагрузкой фейк всё равно должен моделировать задержки и сбои: спать от 200 до 900 мс с джиттером, возвращать 429 на 2 процентах вызовов и 500 на 0,5 процента. Мок, который всегда отдаёт 200 за 1 мс, прячет ровно те таймауты и штормы ретраев, ради которых вы и затеяли нагрузочный тест.
Тестирование лимитов частоты синтетическими номерами
Логика рейт-лимита опирается на строку номера телефона, IP и отпечаток устройства. Ни за одним из этих ключей не обязан стоять арендуемый номер. Сгенерируйте валидные строки E.164 в диапазоне, который ваш провайдер никогда не выдаст, отправьте 60 запросов на один и тот же номер за 60 секунд и проверьте, что четвёртый возвращает 429 с заголовком Retry-After. Сделайте то же самое по 500 разным синтетическим номерам с одного IP, чтобы проверить корзину по IP. Пометьте эти записи как synthetic=true, чтобы никто потом не отлаживал пропавшую SMS для номера, который никогда не арендовали.
Выборочная проверка реальных доставок на малом объёме
Реальным активациям место в ночной задаче, а не в наборе тестов на каждый коммит. Соберите выборку по осям, которые действительно различаются: от 3 до 5 стран, ваши топ-3 сервиса и один кейс с повторной попыткой. От двенадцати до двадцати активаций за ночь ловят сломавшийся sender ID или страну, которая перестала принимать сообщения от сервиса, а такое не поймает ни один мок. Поскольку у каждого арендованного номера есть окно активации, а цена автоматически возвращается на баланс, если SMS не пришла, ночь с мёртвым маршрутом стоит почти ничего. Переиспользуйте цикл опроса на Python или Go из вашего существующего набора тестов, чтобы ночная задача отличалась конфигом, а не кодом.
Оценка стоимости выборки доставки
Посчитайте арифметику до того, как что-то планировать. Возьмите число активаций за прогон, умножьте на число прогонов за период и умножьте на цену для этого сервиса и страны. Цены начинаются от $0.04 и различаются, поэтому берите самую высокую цену в своей матрице, а не самую низкую.
| Периодичность | Активаций за прогон | Прогонов в месяц | При $0.04 | При $0.25 |
|---|---|---|---|---|
| Ночная выборка | 15 | 30 | $18.00 | $112.50 |
| Недельная глубокая матрица | 60 | 4 | $9.60 | $60.00 |
| Гейт на релиз | 25 | 8 | $8.00 | $50.00 |
Возвраты за неудавшиеся активации опускают реальную цифру ниже этих значений. Всё равно закладывайте в бюджет потолок, а затем поставьте оповещение о пополнении на 40 процентах от него.
Ограничители, которые останавливают разогнавшийся тестовый цикл
- Ограничьте число активаций на один прогон процесса прямо в коде, например 40, и жёстко завершайте работу, когда счётчик срабатывает.
- Ограничьте число активаций в час на проект CI, сверяясь с общим счётчиком, а не с локальной переменной.
- Не запускайте прогон, если баланс аккаунта ниже расчётного потолка, чтобы частичный прогон не оставил половину матрицы непроверенной.
- Поставьте отсечку по времени на 15 минут, ведь зависший цикл опроса арендует номера быстрее, чем успешный.
- Освобождайте каждую активацию номера в блоке
finally, чтобы выброшенное исключение в проверке не оставило аренду с открытым окном. - Роняйте сборку при срабатывании ограничителя, а не пропускайте её молча, потому что тихий скип выглядит как зелёный тест доставки, который на деле не запускался.
Как отслеживать доставку OTP в продакшене?
Набор CI-тестов доказывает, что сценарий работал на момент коммита. Мониторинг в продакшене доказывает, что он всё ещё работает в два часа ночи во вторник, когда оператор в одной из стран начинает молча отбрасывать трафик с одного sender ID. Именно в разрыве между этими двумя вещами и живут обращения в поддержку, а лечится он небольшим набором метрик с порогами, по которым вы действительно поднимаете дежурного.
Минимальная тестовая матрица для каждого рынка
| Параметр | Значения для проверки | Почему |
|---|---|---|
| Страна | Каждый рынок запуска | Форматы, префиксы и фильтрация различаются |
| Состояние номера | Свежий, ранее использованный, недавно освобождённый | Обработка дублей |
| Тайминг | Сразу, на грани истечения, после истечения | Логика окна |
| Попытки | Первая, повторная, превышение лимита | Поведение блокировки |
| Канал | SMS, голосовой резерв, если он есть | Резервный путь |
Четыре самые важные метрики
Отслеживайте четыре числа по часам, с разбивкой по странам и провайдерам. Всё остальное это уже детализация.
| Метрика | Определение | Почему меняется |
|---|---|---|
| Доля принятых отправок | Запросы к SMS-провайдеру, вернувшие успешный статус / все попытки отправки | Сбой у провайдера, истёкшие креденшелы, некорректные номера |
| Доля доставки | Отчёты о доставке со статусом delivered / принятые отправки | Фильтрация у оператора, просроченная регистрация sender ID, классификация как спам |
| Время до первого кода (p95) | Секунды от запроса на отправку до события доставки | Затор в очереди у агрегатора, переключение маршрута |
| Завершённость верификации | Пользователи, отправившие верный код / пользователи, запросившие код | Любая из причин выше, плюс UX и частота опечаток |
Доля принятых отправок и доля доставки отвечают на разные вопросы. Провайдер может принять 100% ваших запросов и доставить из них 60%, и узнаете вы об этом только из отчёта о доставке.
Доля доставки по странам и операторам
Агрегированная доля доставки прячет именно тот сбой, который важен. Если 85% вашего трафика это номера США с доставкой 97%, а 6% приходится на одного индийского оператора с 40%, глобальная цифра покажет 93% и никто ничего не заметит. Разбивайте по странам, затем по операторам или по MCC-MNC, если провайдер их отдаёт, и настраивайте алерты на конкретный срез, а не на общий итог.
Задайте минимальный порог объёма, ниже которого срез не может поднять алерт. Десять отправок за час с доставкой 70% это шум. Пятьдесят отправок с 70% против базовой линии 96% за 14 дней это проблема маршрута. Сравнивайте каждый срез с его собственной скользящей базовой линией, потому что страна, у которой норма 88%, не должна будить дежурного за то, что сегодня у неё снова 88%.
Время до первого кода как перцентиль, а не среднее
Среднее время до первого кода почти бесполезно. Большинство кодов приходит меньше чем за 10 секунд, поэтому горстка доставок по 90 секунд едва сдвигает среднее, но полностью убивает опыт всем, кто попал в этот хвост. Отслеживайте p50, p95 и p99 по отдельности.
Следите за p95 относительно таймера кнопки повторной отправки. Если повтор разблокируется на 30-й секунде, а p95 доходит до 34, вы только что создали волну дублирующих отправок, удвоили расходы и получили второй код, который аннулирует первый, тот самый, который пользователь как раз вводит. Это проблема доставки, которая приходит в поддержку в формулировке «код неверный».
Воронка завершённости верификации
Инструментируйте пять шагов и храните счётчики как воронку:
- Номер телефона отправлен.
- Запрос на отправку принят провайдером.
- Получен отчёт о доставке.
- Код введён пользователем.
- Код принят вашим бэкендом.
Потеря между шагами 3 и 4 это внимание и UX, включая то, работает ли автозаполнение. Потеря между 4 и 5 это коды, пришедшие слишком поздно, коды, переписанные из более старой SMS, или расхождение часов в вашем TOTP-пути, если вы также поддерживаете приложения двухфакторной аутентификации. Разделение этих двух потерь избавляет мобильную команду и команду мессенджинга от спора об одной смешанной цифре.
Пороги алертов и что выносить в пейджер
Поднимайте дежурного только по тому, что человек способен исправить в следующие 30 минут. Всё остальное в тикеты.
| Сигнал | Порог | Действие |
|---|---|---|
| Доля принятых отправок | Ниже 95% в течение 10 минут | Пейджер |
| Доля доставки, любая страна с 50+ отправок в час | На 15 пунктов ниже своей базовой линии за 14 дней два окна подряд | Пейджер |
| Время до первого кода p95 | Выше таймера повторной отправки в течение 15 минут | Пейджер |
| Завершённость верификации | Падение на 10 пунктов неделя к неделе | Тикет |
| Доля доставки у отдельного оператора | Ниже 60% в течение 2 часов | Тикет |
| Задержка вебхуков провайдера | Отчёты приходят с опозданием больше 5 минут | Тикет |
Требуйте два окна подряд, прежде чем поднимать дежурного по доле доставки. Операторы дают короткие просадки, которые проходят сами, а срабатывание по одному окну учит дежурного игнорировать алерт.
Ритуал недельного разбора на 20 минут
- Откройте таблицу доли доставки, отсортированную по объёму, топ-10 стран, и отметьте каждый срез, сдвинувшийся больше чем на 5 пунктов.
- Сверьте p95 времени до первого кода по странам с прошлой неделей.
- Прочитайте воронку завершённости и найдите самую крупную единичную потерю.
- Арендуйте по одному номеру на каждую проблемную страну и пройдите сценарий вручную, взяв временный номер телефона, чтобы проверка стоила несколько центов, а не эскалации в поддержке.
- Заведите один тикет по худшему срезу, с указанием страны, оператора, примеров отчётов о доставке и таймстемпов.
Шаг 4 это как раз тот, который команды пропускают. Прогон реального сценария на реальном номере по подозрительному маршруту превращает «доставка в Бразилии выглядит плохо» в «код приходит за 45 секунд, а наш таймер повторной отправки стоит на 30», а это уже формулировка, которую можно починить.
Как добиться стабильных OTP-тестов в CI?
Набор OTP-тестов, который работает с реальными номерами и реальными операторами, никогда не будет таким же детерминированным, как юнит-тест. Цель не в нулевом разбросе, а в том, чтобы красная сборка означала поломку в вашем коде, а не медленную очередь оператора в три часа ночи. Для этого нужно разделить набор тестов по стоимости и нестабильности, а затем выводить достаточно контекста, чтобы тот, кто откроет упавший тест, смог принять решение за две минуты.
Сбои, которые стоит тестировать отдельно
| Сбой | Как воспроизвести | Правильное поведение |
|---|---|---|
| SMS не пришло вовсе | Дать окну активации истечь | Предлагается повтор, ничего не списано, нет наполовину созданного аккаунта |
| Позднее SMS | Код приходит после вашего таймаута | Либо принять его, либо чётко объяснить, почему нет |
| Дублирующийся запрос | Нажать «Отправить» дважды | Один код, а не два конкурирующих |
| Неоднократный неверный код | Ввести четыре неверных кода | Блокировка считается по номеру, сообщение объясняет, что делать |
| Повторно используемый номер | Регистрация на номер, который уже существует | Ваш путь объединения или отклонения, но никогда — трассировка стека |
| Несоответствие страны | Номер из неподдерживаемого рынка | Сообщение с перечислением поддерживаемых |
Какие тесты нужны в каждом пул-реквесте
Прогон на пул-реквесте должен укладываться в 10 минут и не стоить ничего. На каждом PR держите вот что:
- Контрактные тесты против замоканного SMS-провайдера: проверяют, что ваш код отправляет запрос правильной формы и разбирает код правильного формата.
- Тесты конечного автомата сессии верификации: создана, код отправлен, код введён, подтверждена, истекла, заблокирована.
- Логику лимитов и таймера повторной отправки с подменёнными часами, чтобы окно повторной отправки в 30 секунд проверялось за 3 миллисекунды.
- Пограничные случаи валидации кода: неверный код, просроченный код, повторно использованный код, код с пробелами, код из предыдущей сессии.
- По одной записанной HTTP-фикстуре на каждый эндпоинт провайдера, обновляемой раз в неделю, чтобы дрейф схемы проявлялся как диф.
Ни один из этих тестов не арендует номер. Если PR затрагивает сам SMS-адаптер, добавьте один живой смоук-тест под меткой, чтобы контрибьюторы могли включить его по желанию.
Какие тесты нужны в ночной задаче
Тесты реальной доставки должны идти по расписанию, а не на каждый пуш. Ночной прогон, который арендует от 6 до 10 номеров, стоит несколько десятков центов при цене от $0.04 за номер и даёт ежедневный сигнал по тем маршрутам, с которыми вы реально работаете.
| Тип теста | Триггер | Реальный номер | Обычное число за прогон |
|---|---|---|---|
| Контрактный на моках | Каждый PR | Нет | от 40 до 200 |
| Живой смоук, одна страна | Мерж в main | Да | 1 |
| Живая матрица, топ-страны | Ночью | Да | от 6 до 12 |
| Полный обход по странам | Раз в неделю | Да | от 30 до 60 |
| Нагрузка и пропускная способность | По запросу | Частично | По-разному |
Расставьте страны в ночной матрице так, чтобы рынки с наибольшим объёмом шли первыми. Если задача оборвётся, вы всё равно будете знать, работала ли доставка в Соединённые Штаты и Индию.
Как безопасно хранить API-ключи и ограничивать баланс
Держите API-ключ в хранилище секретов CI, никогда в репозитории и никогда в файле фикстуры. Три правила, которые предотвращают большинство инцидентов:
- Используйте для CI ключ, отличный от того, что работает в продакшен-мониторинге, чтобы отзыв одного не ослеплял другой.
- Маскируйте ключ в логах и вычищайте из тела ответа любой номер телефона до того, как он попадёт в лог сборки.
- Ограничивайте риск балансом, а не доверием. Держите на CI-аккаунте небольшой рабочий баланс и пополняйте его по расписанию, чтобы зациклившийся код упёрся в баланс, а не крутился всю ночь.
Добавьте предварительный шаг, который проверяет баланс до старта матрицы и сразу падает с внятным сообщением, если денег меньше ожидаемой стоимости прогона. Сборка, падающая через 5 секунд с текстом «баланс $0.31, для прогона нужно $0.90», лучше той, что падает на середине с 14 непонятными ошибками.
Карантин для нестабильных тестов доставки
Задержка у оператора реальна, и это не ваш баг. Решайте её политикой, а не ретраями повсюду:
- Задавайте время ожидания SMS по измеренным данным, а не наугад. Если медиана прихода 12 секунд, а p95 равен 48, ждите 90.
- Повторяйте живой тест максимум один раз и только по таймауту, никогда по несовпадению кода.
- Если активация истекла и SMS не пришло, считайте результат неопределённым, а не проваленным. Арендованный номер отменяется, а стоимость возвращается на баланс, так что повтор стоит одну дополнительную активацию, а не две.
- Отслеживайте долю нестабильных прогонов по каждому тесту за 14 дней. Всё, что выше 10 процентов, уходит в карантинную задачу: она продолжает запускаться и отчитываться, но не блокирует пайплайн.
- Разбирайте карантин раз в неделю. Тест, который лежит там месяц, это либо настоящий баг продукта, либо маршрут, который пора перестать поддерживать.
Как оформлять результаты, чтобы по ним можно было действовать
Сообщение об ошибке здесь и есть главный продукт. Каждое падение живого теста должно печатать сервис, страну, ID активации, время аренды номера, время истечения ожидания и число прошедших секунд. Прикладывайте сырое тело сообщения, если оно пришло, но не распарсилось, с замаскированными цифрами. Выгружайте результаты в JUnit XML, чтобы CI группировал их по странам, и отправляйте в командный чат по одной итоговой строке на ночной прогон: с цифрами, а не с эпитетами.
Пример схемы пайплайна
jobs:
unit: # каждый PR, моки, ~4 мин
contract: # каждый PR, записанные фикстуры
smoke-live: # мерж в main, 1 номер, US
matrix-live: # ночной cron, 8 номеров, 6 стран
quarantine: # ночью, не блокирует, только отчёт
Держите живые задачи в одном файле с общим шагом подготовки, который арендует номер, опрашивает дашборд или API на предмет кода и закрывает сессию в конце. Хелпер для опроса это тот же код, что использует ваше приложение, так что тот же пример приёма SMS на Python или Node.js работает как фикстура для CI, достаточно поднять константу ожидания.
Как команде организовать общий доступ к номерам, ключам и бюджету?
Тестовые номера превращаются в хаос, когда у каждого из четырёх инженеров свой личный баланс и никто не понимает, какой прогон сколько потратил. Решение простое: один общий аккаунт, ключи с разделением по окружениям и правило именования, которое связывает каждый арендованный номер с тем набором тестов, который его запросил.
Что логировать при каждой попытке верификации
| Поле | Почему это важно |
|---|---|
| Request id | Связывает попытку с логами провайдера |
| Страна и префикс | Показывает, какие рынки дают сбои |
| Провайдер и маршрут | Позволяет сравнить доставляемость между поставщиками |
| Время до первого ввода кода | Реальная задержка, видимая пользователю |
| Результат | доставлено, истекло, неверный код, отказ |
Агрегируйте это по странам и часам; проблема с доставкой проявляется как «затихшая» страна.
Один аккаунт и отдельные ключи для каждого окружения
Заведите единый аккаунт MarioSMS, владельцем которого будет команда, а не конкретный человек, который может уйти в другую компанию. Затем выпустите отдельные API-ключи для локальной разработки, CI и стейджинга. Баланс общий, ключи разные, так что один можно отозвать, не ломая два остальных.
| Ключ | Где используется | Обычный объём | Ротация |
|---|---|---|---|
local | Ноутбуки разработчиков | от 1 до 5 номеров в день | При уходе сотрудника |
ci | Секрет в раннере пайплайна | от 10 до 40 в день | Раз в квартал |
staging | Регулярные smoke-прогоны | от 4 до 8 в день | Раз в квартал |
load | Эксперименты с пропускной способностью | Всплески, затем простой | После каждого эксперимента |
Храните ключи в менеджере секретов, но никогда в репозитории. Локальные ключи кладите в файл .env, добавленный в .gitignore, а ключ для CI держите в замаскированной переменной, чтобы он не попадал в логи задач.
Как считать расходы по каждому набору тестов
Номера стоят от $0.04, и цена зависит от сервиса и страны, поэтому набор тестов, который арендует 30 номеров за ночь, обойдётся не в ту же сумму, что набор, арендующий 30 номеров в более дорогой стране. Записывайте стоимость в момент аренды, а не в конце месяца.
- Пусть хелпер аренды пишет по одной строке на каждую активацию: время, название набора тестов, сервис, страна, цена, ID активации.
- Отправляйте эти строки туда же, куда попадают артефакты тестов (для начала достаточно CSV в выводе задачи).
- Раз в неделю суммируйте по наборам и сравнивайте со списанием баланса в панели.
- Разбирайтесь с любым набором, где количество выросло больше чем на 20 процентов неделя к неделе.
Здесь важны возвраты. Если SMS не приходит в течение окна активации, активация отменяется и стоимость автоматически возвращается на баланс, поэтому учтённые расходы будут завышены, пока вы не пометите отменённые строки. Добавьте столбец refunded и сверяйте его после каждого прогона.
Пополнение картой или криптовалютой и когда его делать
Баланс пополняется картой или криптовалютой. Выберите порог, который покрывает полную неделю работы самого прожорливого набора тестов, и пополняйте баланс, когда он опускается ниже. Если ночные прогоны используют 40 номеров, а средняя цена около $0.10, неделя обойдётся примерно в $28, так что порог в $50 даёт реальный запас и при этом не замораживает лишние деньги.
Поставьте напоминание в календаре, а не ждите провалившейся аренды в CI в два часа ночи. Пустой баланс превращается в красный пайплайн, который двадцать минут выглядит как баг приложения, пока кто-нибудь не заглянет в панель.
Как передать прогон между QA и разработкой
Когда QA находит сломанный сценарий верификации, разработке нужны четыре вещи, чтобы его воспроизвести: страна, сервис, ID активации и точный текст SMS. Сделайте их обязательными полями в шаблоне тикета. ID активации здесь опорная точка, потому что он связывает прогон с конкретным арендованным виртуальным номером телефона и его таймером.
Скриншоты экрана приложения помогают, но сырой текст сообщения помогает больше: ID отправителя и длина кода различаются в зависимости от маршрута, и как раз в этой разнице часто и кроется сам дефект.
Как фиксировать, какие номера использовались и зачем
Держите в репозитории обычную таблицу, которую обновляет хелпер аренды, а не человек вручную. Столбцы: дата, набор тестов, страна, сервис, ID активации, результат (код получен, истекло время, возврат). Храните её от 30 до 90 дней, потом удаляйте, потому что это одноразовые операционные данные, а долгое хранение создаёт проблему с приватностью, которая вам совершенно не нужна. Каждый номер арендуется под одну верификацию за раз, так что в журнале остаётся одна строка на попытку.
Используйте всё это только для защиты приватности, для вторых аккаунтов, которыми ваша команда владеет на законных основаниях, и для тестирования QA или OTP, согласно правилам допустимого использования.
Приложения для iOS и Android для ручных прогонов
Автоматизация закрывает повторяющиеся сценарии, но исследовательское тестирование по-прежнему делается руками. Приложения для iOS и Android, а также веб-версия на app.mariosms.com показывают один и тот же баланс и один и тот же список активаций, так что тестировщик с телефона арендует номер, дожидается кода, обычно в течение минуты, и вставляет его в тестируемое приложение, не открывая терминал. Выберите страну из доступных в наличии по 35+ странам, нажмите на аренду, и активация появится в общей панели, где её увидит вся остальная команда.
Какие ограничения по приватности и закону действуют при тестировании верификации?
Арендованные номера это обычная часть тестового набора, и ответственность за них такая же, как за любые другие учётные данные, с которыми работает команда. Ограничения несложные, но они должны быть где-то записаны, чтобы ваши QA-инженеры, подрядчики и новые сотрудники могли прочитать их до того, как арендуют свой первый номер.
Метрики, по которым стоит настроить алерты в продакшене
| Метрика | Здоровая форма | Когда алертить |
|---|---|---|
| Доля введённых кодов | Стабильна по странам и часам | Падает на треть по сравнению с тем же часом неделю назад |
| Медианное время до ввода | Десятки секунд | Удваивается |
| Истёкшие активации | Небольшая стабильная доля | Резкий скачок в одной стране |
| Доля повторов | Низкая | Растёт без релиза |
Гигиена приватности как основной сценарий
Большинство команд начинают арендовать номера потому, что не хотят видеть личные телефоны в тестовых данных. QA-лид, который зарегистрировал двенадцать аккаунтов на стейджинге со своего личного номера, в итоге получает этот номер в двенадцати базах данных, часть из которых будет выгружена, а часть окажется в резервных копиях там, где их никто не отслеживает. Дальше приходят маркетинговые рассылки. А если какую-то из этих систем взломают, то и попытки сброса пароля.
Арендованный номер разрывает эту цепочку. Он обслуживает одну верификацию, активация закрывается, и номер возвращается в пул. Ничто не связывает тестовый аккаунт с контактными данными живого человека. Та же логика работает для основателя, который проверяет онбординг конкурента, или для инженера поддержки, воспроизводящего баг клиента: им нужен рабочий номер, а не свой собственный.
Вторые аккаунты по законным причинам
Немало легитимных задач требует больше одного аккаунта. Соцмедиа-менеджер ведёт бренд-аккаунт и личный. Разработчик держит песочницу отдельно от продакшен-аккаунта на той же платформе. Команде поддержки нужен аккаунт со стороны клиента, чтобы видеть то же, что видят пользователи. Тестировщику локализации нужен аккаунт, зарегистрированный в том рынке, который он проверяет, а это обычно означает номер из этой страны, например индийский номер или номер США.
Проверьте условия сервиса, в котором вы регистрируетесь. Где-то несколько аккаунтов разрешены прямо, где-то разрешены при наличии деловых отношений, где-то ограничены. Источник номера никак не меняет то, что написано в этих условиях.
QA и тестирование OTP на системах, которыми вы владеете или которые вам разрешено тестировать
Самый чистый случай это тестирование собственного продукта. Вы контролируете отправителя, формат кода, политику повторных отправок и жизненный цикл аккаунта, так что тест не касается никаких третьих сторон, кроме маршрута оператора. Записывайте тестовые аккаунты в базу стейджинга, помечайте их тегами и удаляйте по расписанию.
Когда вы тестируете чужую систему, сначала получите разрешение в письменном виде. Подписанный скоуп пентеста, договор с клиентом, в котором перечислены системы, или программа bug bounty с опубликованной политикой: всё это подходит. Устное разрешение от человека, который не владеет системой, не подходит. Лимиты запросов и защита от злоупотреблений на OTP-эндпоинтах существуют не просто так, и долбить эндпоинт, тестировать который вас не приглашали, это проблема независимо от того, откуда взялись номера.
Что недопустимо никогда
Две вещи всегда вне закона. Первая это возврат в аккаунт, из которого вас забанили, независимо от того, за что был бан: за спам, мошенничество, травлю или нарушение условий. Бан это решение платформы о вас, а не о вашем телефонном номере. Вторая это выдавать себя за другого: регистрироваться на имя реального человека, присваивать чужую личность или создавать аккаунты, сделанные так, чтобы их принимали за компанию или человека, которыми вы не являетесь.
Мошенничество, спам-кампании и всё, что направлено против другого человека, тоже выходит за рамки допустимого использования. MarioSMS блокирует аккаунты за такое. Полная политика находится по адресу /acceptable-use/.
Хранение тестовых артефактов
Прогоны тестов OTP порождают артефакты: логи с номерами, скриншоты экранов верификации, вывод CI-задач, строки в базе с тестовыми аккаунтами. Относитесь ко всему этому как к персональным данным, даже если номера арендованные.
| Артефакт | Рекомендуемый срок хранения | Обращение |
|---|---|---|
| CI-логи с ID активаций | 14 до 30 дней | Маскировать последние 4 цифры номеров |
| Скриншоты неудачных прогонов | 7 дней | Автоудаление из бакета с артефактами |
| Строки тестовых аккаунтов на стейджинге | До закрытия спринта | Удалять по тегу, а не вручную |
| Тексты полученных сообщений с кодами | Не хранить | Проверить извлечённый код и сбросить текст |
Сами коды живут недолго, часто 5 до 10 минут, но сохранённый текст SMS, который год лежит в логе, остаётся записью сообщения, отправленного на реальный номер.
Как направить команду к странице допустимого использования
Поставьте ссылку в трёх местах: в онбординг-документ для новых QA-сотрудников, в README тестового репозитория, который обращается к API, и в закреплённое сообщение в том канале, где ваша команда запрашивает пополнение бюджета. Когда кто-то спрашивает, разрешён ли конкретный сценарий, отвечайте по тексту политики, а не по памяти, а если случай действительно неоднозначный, спросите поддержку до аренды, а не после того, как аккаунт уже создан.
Частые вопросы о тестировании OTP и SMS-подтверждений
Как быстро обычно приходит тестовый код?
Чаще всего код появляется в приложении или в личном кабинете в течение минуты после отправки запроса. Разброс зависит от маршрута, а не от аренды номера. Внутренний маршрут до крупного оператора нередко доставляет сообщение за 5 до 15 секунд. Трансграничный маршрут с несколькими промежуточными узлами может занять 40 секунд и больше. Ставьте таймаут опроса на 120 секунд и считайте всё, что быстрее, приятным бонусом, а не гарантией.
Как сделать набор тестов стабильным в CI
| Проблема | Решение |
|---|---|
| Нестабильные таймауты | Ставьте таймаут теста дольше окна активации, а не короче |
| Столкновения при параллельных прогонах | Один номер на воркер, освобождается в teardown |
| Разрастание бюджета | Ограничьте число прогонов в день, используйте самый дешёвый рынок для дымовых тестов |
| Секреты в логах | Скрывайте номера и коды в выводе тестов |
Что будет, если SMS вообще не придёт?
У каждого номера есть окно активации. Если окно закрылось, а SMS не пришла, активация отменяется и деньги автоматически возвращаются на баланс. Не нужно писать в поддержку и ждать ручной проверки. Для набора тестов это важнее, чем кажется: сломанная сборка, которая запустит 200 неудачных активаций, не будет стоить ничего, кроме потраченных минут. Но всё равно логируйте отмену как провал теста, ведь отсутствие кода это реальный сигнал о состоянии отправителя.
Может ли один номер принять два кода?
Нет. MarioSMS выдаёт номер в аренду для одного подтверждения за раз. Повторная отправка внутри того же окна активации иногда приходит на тот же номер, но строить на этом логику не стоит. Если ваш тест-кейс описывает сценарий «пользователь дважды нажимает „отправить повторно“ и вводит второй код», вынесите путь с повтором в отдельный сценарий и проверьте, не нужна ли для него новая аренда. Исходите из правила «один номер, один код», и тест останется детерминированным.
Сколько стоит один тестовый номер?
Цены начинаются от $0.04 и зависят от сервиса и страны. Дешёвый сервис в стране с большим предложением номеров стоит около нижней границы. Номер для сервиса со строгими правилами к отправителям на небольшом рынке обойдётся дороже. Оценивайте стоимость прогона, умножая количество аренд за один запуск на самую высокую цену в вашей матрице, а не на самую низкую, и сравнивайте это с ценой рабочего времени инженера, который гоняется за нестабильным ручным тестом.
Нужен ли отдельный номер для каждого тест-кейса?
Да, для всего, что создаёт аккаунт, и да, для всего, что проверяет ветку «этот номер уже зарегистрирован». Повторное использование ведёт к взаимному влиянию тестов, когда кейс B проходит только потому, что кейс A оставил после себя состояние. Чистые проверки доставки (доходит ли SMS по этому маршруту вообще) могут делить одну аренду внутри одной активации, но это единственное безопасное пересечение.
Как протестировать истечение срока кода, не дожидаясь его?
Три варианта, в порядке предпочтения:
- Сдвиньте время на бэкенде. Через тестовый эндпоинт переведите поле
created_atу записи OTP назад, а затем отправьте код. - Сократите TTL в тестовом окружении. Срок жизни в 30 секунд даёт полноценный тест по реальным часам меньше чем за минуту.
- Арендуйте номер, получите код и подержите его дольше реального окна. Медленно и дорого, зато это единственный вариант, который проверяет продакшен-TTL ровно в том виде, в каком он выпущен.
Вариант 1 используйте для CI, вариант 3 один раз на релиз.
Можно ли автоматизировать весь сценарий через API?
Да. REST API покрывает аренду, получение кода и отмену, с примерами на Python, Node.js, Go и PHP. Рабочий автоматический кейс это четыре вызова: запросить номер, инициировать отправку в своём приложении, опрашивать до получения кода, освободить номер или дать активации истечь. Оберните это в фикстуру, и тест будет читаться как любой другой интеграционный.
Сколько стран доступно?
В наличии 35+ стран и сотни сервисов, причём доступность меняется вместе с предложением. Проверяйте наличие в момент аренды, а не зашивайте страну в конфиг теста. Если ваша матрица требует конкретного рынка, добавьте условие пропуска, чтобы набор тестов сообщал «нет номеров для XX» вместо красного падения, на отладку которого инженеры потратят час.
Принимают ли SMS эмуляторы и симуляторы?
Настоящие нет. У iOS Simulator нет ни радиомодуля, ни SIM-карты. Android-эмулятор принимает SMS, подставленную через консоль (sms send на порту 5554), что проверяет вашу логику разбора и автозаполнения, но ничего не говорит о доставке через оператора. Разделите задачи: подставленные сообщения для поведения интерфейса и арендованный реальный номер для проверки доставки.
Как протестировать резервный звонок?
Резервный вариант обычно срабатывает после неудачной попытки отправить SMS или когда пользователь нажимает «позвонить вместо этого». Чтобы надёжно попасть в эту ветку, дайте попытке отправки SMS истечь по таймауту, а не пытайтесь искусственно вызвать ошибку. Убедитесь, что приложение показывает элемент резервного варианта в нужный момент, а затем отдельно проверьте настройки голосового провайдера. Номера, арендованные для SMS, принимают текст, поэтому проверки звонков планируйте через панель собственного провайдера.
Стоит ли тестировать на личном номере?
Нет, по четырём причинам: на вашем номере накапливается история регистраций, которая искажает результаты; путь «новый пользователь» нельзя пройти дважды; коллеги не смогут воспроизвести ваш прогон; а личный номер попадёт в общие логи CI. Используйте арендованные номера, а личный держите вне репозитория.
Что должно быть в баг-репорте о неудачном подтверждении?
| Поле | Пример |
|---|---|
| Время (UTC) | 2026-09-10 14:22:05 |
| Страна и сервис | Индия, мессенджер |
| Номер (только последние 4) | …4471 |
| ID активации | act_8812f |
| Статус запроса на отправку | HTTP 200, отправитель принял |
| Время до таймаута | 118 с |
| Замеченный Sender ID (если был) | нет |
| Сборка приложения | 4.7.1 (2211) |
Восемь полей, и поддержка или ваш SMS-провайдер смогут отследить маршрут без дополнительной переписки.
Как протестировать ограничение частоты запросов и не получить блокировку?
Направьте тесты лимитера на собственный бэкенд с заглушкой вместо отправителя SMS. Подсчёт запросов это ваша логика, и настоящее сообщение для неё не нужно. Отправьте 10 запросов за 60 секунд на заглушку, проверьте код 429 и заголовок retry-after, а затем прогоните один реальный арендованный номер по успешному сценарию, чтобы убедиться, что лимитер не блокирует легитимный трафик.
Можно ли использовать номер повторно на следующей неделе?
Исходите из того, что нет. Одна аренда покрывает одну активацию, и тот же номер вас потом не дождётся. Проектируйте тесты без привязки к личности: никаких фикстур, ожидающих конкретную строку с номером, никаких эталонных файлов с номером внутри, никаких аккаунтов, которые должны сохраняться между прогонами. Создавайте личность в момент теста и удаляйте её в конце.
Пройдите предстартовый чек-лист, прежде чем запускать верификацию по номеру телефона
Распечатайте его, пройдите сверху вниз и ставьте галочки только после того, как своими глазами увидели нужное поведение в реальной среде. Каждый пункт ниже - это то, что хотя бы одна команда уже когда-то выкатила сломанным.
На что уходят деньги в тест-плане
| Прогон | Использовано номеров | Примерная стоимость |
|---|---|---|
| Дымовой тест на каждый деплой | 1–2 | Центы |
| Полная матрица на рынок | 6–10 | Меньше доллара |
| Еженедельная регрессия, 8 рынков | 60–80 | Несколько долларов |
| Нагрузочный тест, 1000 регистраций | 1,000 | Десятки долларов по ценам каталога |
Цены каталога, 2026-09-10; неудавшиеся активации возвращаются, так что реальный счёт ниже.
Проверки генерации и хранения кодов
- Коды берутся из криптографически стойкого источника случайности, а не из
Math.random()или PRNG с сидом. - Длина кода и алфавит зафиксированы и задокументированы (6 цифр - привычное значение по умолчанию для сценариев с OTP).
- Код хранится в виде хеша, а в записи привязаны номер телефона и назначение кода, чтобы код, выпущенный для входа, нельзя было переиспользовать для сброса пароля.
- Срок жизни хранится как абсолютная метка времени, проверяется на стороне сервера и соблюдается даже тогда, когда клиент присылает устаревшее значение.
- Верификация одноразовая. Запись помечается использованной в той же транзакции, в которой выдается сессия.
- Сравнение выполняется за постоянное время, чтобы по времени ответа нельзя было понять, сколько первых цифр совпало.
- Номера телефонов приводятся к E.164 перед сохранением и перед поиском, чтобы
+1 415 555 0142и4155550142вели к одной записи.
Проверки доставки и запасных путей
- У вызова отправки есть таймаут (от 5 до 10 секунд) и политика повторов, которая не отправляет сообщение дважды при медленном, но успешном ответе.
- Ошибки провайдера сопоставлены с понятными пользователю состояниями: неверный номер, страна не поддерживается, отказ оператора, провайдер недоступен.
- Кнопка повторной отправки есть, она заблокирована первые 30-60 секунд и переиспользует тот же код, а не выпускает новый при каждом нажатии.
- Проложен хотя бы один запасной путь (второй провайдер, голосовой звонок или email), и вы запускали его вручную, а не просто читали о нем.
- Длинные тексты сообщений, нелатинские sender ID и юникод в шаблоне отправлены на реальный телефон в каждой стране запуска.
- Отчеты о доставке принимаются и сохраняются, так что «отправлено» и «доставлено» - это два разных состояния в ваших данных.
Проверки защиты от злоупотреблений и лимитов
- Лимиты заданы на номер телефона, на IP, на аккаунт и на отпечаток устройства, у каждого свое окно.
- Ответ на попытку перебора одинаков независимо от того, зарегистрирован номер или нет.
- Заданы потолки расходов на час и на сутки, с жесткой остановкой, а не только с оповещением.
- Дорогие направления вынесены в белый список или заблокированы: накрутка трафика бьет ровно по тем диапазонам, куда вы ничего не продаете.
- Число неудачных попыток ввода ограничено (обычно 5), и после исчерпания лимита запись аннулируется, а не просто отклоняется.
Проверки мониторинга и оповещений
| Сигнал | Оповещать, когда | Почему это важно |
|---|---|---|
| Доля успешных отправок | Падает более чем на 5 пунктов относительно базовой линии за 7 дней | Проблема у провайдера или с аккаунтом |
| Доля доставленных по странам | Любая страна теряет 10 пунктов за час | Фильтрация у оператора |
| Медианное время ввода кода | Превышает 90 секунд | Медленная доставка выше по цепочке |
| Доля завершенных верификаций | Резко падает на одной платформе | Баг клиента, а не проблема доставки |
| Расходы за час | Превышают ваш потолок | Накрутка или цикл повторов |
У каждого оповещения должен быть владелец, а решение «будим человека или заводим тикет» принимается до запуска, а не во время первого инцидента.
Проверки документации и рунбука
- В рунбуке есть страница с названием провайдера, владельцем аккаунта, каналом поддержки и ожидаемым временем ответа.
- Шаги переключения на другого провайдера записаны командами или ключами конфигурации, а не текстом.
- У команды поддержки есть скрипт на случай «мне не пришел код», который охватывает страну, оператора, роуминг, реестры DND и фильтры на телефоне.
- Тестовые доступы, песочные номера и порядок работы с арендованными номерами задокументированы так, чтобы новый инженер мог прогнать набор тестов в первый же день.
- Срок хранения номеров телефонов и записей о кодах записан в днях, а задача на удаление поставлена в расписание.
- Внутри команды четко зафиксирована позиция по допустимому использованию: вторые аккаунты, QA и приватность поддерживаются, обход банов и выдача себя за другого - нет.
Пятиминутный дымовой тест после каждого деплоя
- Арендуйте один номер в MarioSMS в вашем крупнейшем рынке, например в США, за $0.04 и выше в зависимости от сервиса.
- Запустите отправку из задеплоенной сборки и включите таймер.
- Убедитесь, что код появился в личном кабинете в пределах окна активации (обычно меньше минуты; если ничего не пришло, активация отменяется, а деньги возвращаются на баланс).
- Введите код, убедитесь, что сессия выдана, затем отправьте тот же код повторно и убедитесь, что он отклонен.
- Спровоцируйте одну ошибку намеренно (истекший код) и проверьте, что текст ошибки совпадает с тем, что ожидает услышать поддержка.