Самые сложные для воспроизведения сбои в модуле встроенных покупок обычно связаны не с тем, что «кнопка не нажимается», а с утечкой состояния транзакции между тестовыми сценариями: первый тест завершает покупку, а второй, который должен проверять интерфейс до покупки, сразу получает уже разблокированное состояние. При длительном выполнении регрессионных тестов на облачном Mac необходимо одновременно изолировать симулятор, сессию StoreKit и постоянные данные приложения. Иначе отдельные тесты будут проходить, а полный набор — регулярно завершаться с ошибкой.
Сначала разделите тестирование на три уровня
Первый уровень — чистый автомат бизнес-состояний. Он получает состояния «не куплено», «обрабатывается», «куплено», «возвращено» и «истекло», после чего проверяет доступность функций и состояние интерфейса без запуска реальной транзакции. На втором уровне используется StoreKit Testing для проверки запросов товаров, обратных вызовов покупки, наблюдения за транзакциями и изменений подписки. Третий уровень — контролируемое приёмочное тестирование перед выпуском, необходимое для проверки серверных уведомлений и конфигурации реальных товаров.
Такое разделение позволяет выполнять большую часть регрессионных тестов локально и сразу определять, к какой области относится сбой: бизнес-логике, клиентской интеграции с платёжной системой или внешней конфигурации. Не следует начинать каждый UI-тест с нажатия кнопки покупки. Основную часть набора должны составлять модульные тесты автомата состояний, а интеграционные тесты StoreKit — охватывать только критические сценарии.
StoreKit Testing предоставляет управляемую среду транзакций, но не заменяет производственную платёжную цепочку. Успешный тест подтверждает, что клиент правильно реагирует на заданные события, однако не означает, что внешняя конфигурация уже прошла приёмку.
Создайте проверяемый каталог товаров
Создайте в проекте файл StoreKit/Local.storekit. Идентификаторы товаров в нём должны точно совпадать с идентификаторами, используемыми в коде. Рекомендуется определить их централизованно, чтобы они не были разбросаны по представлениям и тестам:
enum ProductID {
static let proMonthly = "com.example.app.pro.monthly"
static let proYearly = "com.example.app.pro.yearly"
}
Файл конфигурации следует хранить в системе контроля версий, а изменения тестовых цен, отображаемых названий и периодов подписки должны проходить ревью кода. Распространённая ошибка — скопировать старую конфигурацию и изменить только отображаемое название, оставив прежний идентификатор товара. В результате запрос товаров возвращает пустой массив.
Создайте для тестов отдельную схему, например StoreKitRegression, и выберите один и тот же файл .storekit в параметрах Run и Test этой схемы. Не полагайтесь на личные схемы разработчиков: xcodebuild обнаруживает только общие схемы.
| Проверка | Ожидаемый результат |
|---|---|
| Идентификатор товара | Полное совпадение в файле конфигурации, коде и утверждениях |
| Scheme | Помечена как Shared и добавлена в репозиторий |
| Группа подписок | Порядок переходов на более высокий и более низкий уровни внутри группы однозначен |
| Локализация | Тестовые утверждения не зависят от часто меняющегося отображаемого текста |
Начинайте каждый тест с чистой сессии
Создавайте тестовую сессию с помощью StoreKitTest, а в setUp отключайте системные диалоговые окна и очищайте транзакции. После завершения тестового метода также удаляйте кэш прав доступа, который сохраняет само приложение.
import XCTest
import StoreKitTest
final class PurchaseRegressionTests: XCTestCase {
private var session: SKTestSession!
override func setUpWithError() throws {
session = try SKTestSession(
configurationFileNamed: "Local.storekit"
)
session.disableDialogs = true
session.clearTransactions()
UserDefaults.standard.removeObject(forKey: "cachedEntitlements")
}
func testMonthlyPurchaseUnlocksPro() async throws {
try await session.buyProduct(
identifier: ProductID.proMonthly
)
let unlocked = await EntitlementStore.shared.refresh()
XCTAssertTrue(unlocked)
}
}
Наблюдатель транзакций необходимо запускать до начала покупки. Если покупка завершается слишком быстро, запущенная позже задача наблюдения может пропустить обновление, из-за чего тест будет периодически завершаться по тайм-ауту. Возврат средств, отзыв покупки и истечение подписки также следует инициировать через сессию, а затем проверять результат после обновления прав доступа, а не ограничиваться проверкой успешного ответа API покупки.
Не маскируйте состояние гонки фиксированными задержками
Task.sleep лишь откладывает сбой, но не доказывает, что состояние действительно обновилось. Надёжнее предоставить наблюдаемое состояние в хранилище прав доступа, дождаться в тесте выполнения конкретного условия и установить короткий тайм-аут. В журнале тайм-аута как минимум должны фиксироваться идентификатор товара, текущие права доступа, количество незавершённых транзакций и название теста. Токены и полные учётные данные выводить нельзя.
Зафиксируйте точку запуска через командную строку
Сначала проверьте, какие устройства симулятора установлены на облачном Mac, а затем настройте CI на использование конкретного имени. Если в образе устройство называется иначе, измените параметры конвейера вместо того, чтобы позволять скрипту без уведомления выбирать произвольное устройство.
xcrun simctl list devices available
xcodebuild test \
-workspace Example.xcworkspace \
-scheme StoreKitRegression \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-resultBundlePath Artifacts/StoreKitTests.xcresult \
CODE_SIGNING_ALLOWED=NO
Если тесты выполняются только в симуляторе, отключение подписывания кода уменьшает количество сбоев, не связанных с логикой транзакций. Перед запуском необходимо очистить каталог результатов либо создавать отдельный каталог для каждого задания. Если файл xcresult уже существует, команда сразу завершится с ошибкой.
Интеграционные тесты StoreKit стабильнее выполнять последовательно. Если требуется параллельный запуск, каждому исполнителю нужны отдельные симулятор, каталог Derived Data и каталог результатов. Когда несколько процессов одновременно работают с одним симулятором, очередь транзакций и данные приложения загрязняют друг друга.
Превращайте каждый сбой в пригодные для диагностики данные
Необходимо охватить как минимум шесть сценариев: первую покупку, отмену пользователем, ожидание обработки, возврат средств, продление подписки и истечение подписки. В каждом сценарии следует проверять три уровня: состояние, возвращённое StoreKit, состояние хранилища прав доступа и итоговое состояние интерфейса. Если проверять только изменение текста кнопки, можно не заметить, что права доступа в фоновом хранилище не обновились.
Перед отправкой изменений выполните проверки в следующем порядке:
- Файл
.storekitи общая схема добавлены в систему контроля версий. - Перед каждым тестом очищаются транзакции и локальный кэш прав доступа.
- Наблюдатель транзакций запускается до выполнения покупки.
- Тесты не зависят от фиксированных задержек или порядка выполнения.
- Каждая параллельная задача использует отдельные симулятор и каталог артефактов.
- При сбое сохраняются
xcresult, журналы тестов и вложения с интерфейсом. - Перед выпуском отдельно проверяются серверные уведомления и конфигурация товаров.
При выполнении таких задач на HireVM сначала проверьте доступные конфигурации в панели управления, а затем спланируйте количество симуляторов в соответствии с числом тестовых сегментов. На тесты встроенных покупок сильнее влияет изоляция состояния, чем скорость отдельной компиляции. Сначала добейтесь стабильно повторяемого прохождения одного последовательного конвейера и только после этого увеличивайте параллелизм — так затраты на диагностику будут значительно ниже.
Часто задаваемые вопросы
Может ли StoreKit Testing полностью заменить проверку реальных покупок?
Нет. Он проверяет соответствие товаров, переходы состояний, интерфейс и ошибки, но серверные уведомления, реальную конфигурацию товаров и полную цепочку транзакции нужно принимать отдельно.
Почему тест проходит отдельно, но падает в полном наборе?
Обычно предыдущий тест оставляет транзакцию, состояние продления или локальные данные приложения. Перед каждым сценарием очищайте сессию и состояние приложения.
Можно ли запускать тесты StoreKit параллельно?
Да, если у каждого процесса есть отдельный симулятор и каталог результатов. Несколько процессов не должны совместно использовать одну сессию StoreKit.
Запустите следующую задачу на удалённом Mac на выделенном узле
Выберите одну из двух доступных конфигураций, пять узлов и период оплаты: день, неделя, месяц или квартал. Перед заказом можно полностью проверить конфигурацию и сумму в USD.