Сначала изолируйте общее состояние, файлы, порты, Keychain и ресурсы симулятора; только затем добавляйте .serialized для узкого участка и проверяйте результат на чистом удалённом Mac. На этой неделе зафиксируйте один коммит, один вход тестов, результаты каждого запуска и состояние узла — не отключайте параллельность во всём проекте.
Эта схема подходит вам, если миграция с XCTest на Swift Testing изменила порядок выполнения и локальные тесты проходят, а удалённый CI периодически выдаёт красный результат. Она также нужна DevOps- и тест-инженерам, которые отвечают за сбор артефактов и изоляцию узла, а также руководителям платформ, выбирающим между рефакторингом, частичной сериализацией и отдельным Mac.
Последнее обновление — 6 сентября 2026 года. Факты о модели параллельного выполнения, .serialized, миграции и результатах тестов сверены с официальной документацией и исходным кодом Swift Testing из списка источников.
Что именно нужно доказать до изменения конфигурации
Нестабильные параллельные тесты Swift Testing не являются автоматически дефектом фреймворка. Один и тот же красный результат может быть вызван четырьмя разными механизмами:
- тесты используют общий объект или глобальное состояние;
- два сценария одновременно обращаются к одному файлу, порту, записи базы данных или ключу Keychain;
- Swift Testing, XCTest, CI-задачи и несколько Runner выполняются параллельно на разных уровнях;
- удалённый Mac перегружен, содержит остатки предыдущего запуска или использует иной Test Plan.
Официальная модель Swift Testing предполагает параллельное выполнение тестов, если ограничения не заданы явно. Это следует проверять по документации о параллельном выполнении Swift Testing, а не выводить из одного случайного падения.
Сначала разделите результаты на стабильный отказ, зависимость от порядка, конкуренцию за ресурс и аномалию среды. Для каждого запуска сохраняйте:
- хеш коммита;
- версию Xcode и используемый Test Plan;
- точку входа CI;
- имя теста и Suite;
- порядок выполнения;
- результатный пакет;
- свободное место, память, активные процессы и параллельные задания на узле.
Повторный запуск сам по себе не исправляет проблему. Его задача — показать, меняется ли результат при сохранении кода и инструментария.
| Наблюдение | Вероятная граница поиска | Первое действие |
|---|---|---|
| Тест падает и при отдельном запуске | Ошибка продукта, фикстуры или окружения | Запустить на чистом рабочем каталоге и проверить входные данные |
| Отдельно проходит, но падает в группе | Общее состояние или порядок | Повторить в случайном порядке и разделить ресурсы |
| Красный результат появляется только на CI | Рабочая область, узел, Test Plan или Runner | Сравнить локальную и CI-конфигурацию |
Падение исчезает после .serialized |
Конкуренция вероятна, но причина не устранена | Найти ресурс, затем убрать временную сериализацию |
| Падают UI-тесты при успешных процессных тестах | Симулятор, окно, служба или отдельный процесс | Развести уровни параллельности |
Почему Swift Testing иногда падает только в CI?
Потому что CI чаще меняет порядок, плотность нагрузки и жизненный цикл рабочей области. Локальный запуск обычно использует один процесс и чистое внимание разработчика, тогда как удалённый Mac может одновременно обслуживать несколько заданий. Это ещё не доказывает неисправность Swift Testing: доказательством станет воспроизводимое различие между изолированным и конкурентным запуском.
02Три уровня параллельности, которые нельзя смешивать
Перед исправлением составьте карту фактических запусков. В Swift Testing есть параллельность внутри тестового процесса. В XCTest действуют собственные правила организации и выполнения тестов. Поверх них работают параллельные CI Job, а несколько Runner могут конкурировать за один Mac.
| Уровень | Что конкурирует | Типичный конфликт | Как проверять |
|---|---|---|---|
| Swift Testing | Тесты и Suite внутри процесса | Глобальная переменная, Singleton, общий кэш | Убрать общий объект и повторить группу |
| XCTest | Тестовые классы, цели и настройки схемы | Разные правила Test Plan или смешанный запуск | Сопоставить Scheme и тестовые цели |
| CI Job | Несколько заданий пайплайна | Общий DerivedData, порт или каталог | Дать каждой Job отдельную рабочую область |
| Runner | Процессы и задания на одном Mac | Память, симулятор, фоновые службы | Проверить процессы и историю узла |
Документация по организации тестов в Xcode нужна для проверки целей и схем. Настройки Test Plan следует сопоставлять отдельно: рекомендации по организации тестов для ускорения обратной связи не заменяют проверку фактической CI-команды.
Сценарий выглядит так: разработчик переносит часть XCTest, локально запускает один Suite и получает зелёный результат. На удалённом Mac CI тот же коммит падает только рядом с другим Suite. Если добавить .serialized на весь проект, красный результат исчезает, но причина остаётся, а время обратной связи растёт. Правильный следующий шаг — определить, какой объект или ресурс связывает эти тесты.
Зона ответственности автора тестов: состояние и жизненный цикл
Тестовый автор начинает не с параметров CI, а с инвентаризации состояния. Ищите глобальные переменные, статические кэши, Singleton, лениво созданные сервисы и объекты, которые передаются из одного теста в другой. Параллельные тесты должны получать независимые фикстуры, а не общий экземпляр с очисткой «когда-нибудь потом».
Проверьте эквиваленты старых setUp и tearDown. При миграции на Swift Testing подготовка и очистка могут быть организованы иначе, поэтому важно установить, когда именно создаётся ресурс и завершилась ли асинхронная задача к моменту окончания теста. Незавершённая Task может продолжить запись в кэш уже после того, как другой тест начал работу.
Практический порядок такой:
- создайте фиктуру внутри теста или Suite с минимальным временем жизни;
- передавайте зависимости явно;
- уберите чтение и запись глобальных флагов;
- дождитесь завершения фоновых Task;
- добавьте уникальный идентификатор запуска в имена временных данных;
- повторите группу в случайном порядке;
- затем запустите каждый ранее упавший тест отдельно.
Описание Swift Testing и документ о миграции с XCTest полезны здесь как справочные материалы по модели фреймворка. Они не доказывают, что конкретная фикстура вашего проекта безопасна для параллельного доступа.
Важно: удаление кэша может скрыть загрязнение рабочей области, но не исправить общий объект. Удаляйте DerivedData или временные каталоги только как отдельный эксперимент и сохраняйте способ возврата к прежнему состоянию.
Как ограничить параллельность только для части тестов?
Добавьте .serialized к узкому Suite или к поддерживаемой параметризованной группе, которая действительно делит ресурс. Официальное описание trait .serialized нужно использовать как источник семантики; не превращайте его в глобальный переключатель проекта.
Обычный тестовый метод не должен получать .serialized только потому, что падение исчезло после изменения. Если конфликт относится к нескольким тестам, ограничение должно отражать их общую границу — чаще это Suite. Если проблема связана с параметризованным тестом, проверяйте поведение именно этой группы и фиксируйте, какие вызовы нельзя выполнять одновременно.
Решение должно содержать условие снятия: например, после удаления общего ресурса, перехода на отдельный временный каталог и успешного прогона на чистом узле. Без такого условия временная мера почти всегда становится постоянной.
04Зона ответственности прикладного инженера: ресурсы вне процесса
Даже полностью независимые объекты Swift Testing могут конфликтовать через операционную систему. Проверьте:
- фиксированный путь во временном каталоге;
- один файл базы данных;
- занятый сетевой порт;
- общие значения
UserDefaults; - записи Keychain;
- уведомления и слушатели;
- один симулятор;
- состояние окна или UI-сессии;
- фоновые процессы приложения.
Для каждого ресурса ответьте на три вопроса: можно ли создавать отдельный экземпляр, кто отвечает за очистку и что происходит при аварийном завершении теста. Уникальное имя файла не решает проблему, если приложение внутри всё равно обращается к фиксированному пути.
Keychain требует отдельной осторожности. Массовое удаление записей перед прогоном способно повлиять на другие задания и усложнить расследование. Сначала используйте отдельный набор ключей или отдельную учётную запись тестового узла. Изменение общего хранилища допустимо только с журналом действий и понятным откатом.
Симулятор и UI нельзя объяснять тем же переключателем, что и процессную параллельность Swift Testing. Внутри процесса тесты могут быть изолированы, но два UI-задания всё равно будут бороться за один экран, симулятор или системное состояние. Для таких задач часто требуется отдельный Job, отдельный симулятор или отдельный удалённый Mac.
05Миграция с XCTest: что передать инженеру CI
При смешанном проекте составьте реестр тестовых целей. Для каждой цели отметьте фреймворк, Scheme, Test Plan, команду запуска и место сохранения результата. Концепция Swift Testing объясняет направление дизайна, а не конфигурацию именно вашего пайплайна.
Сверьте локальный и CI-запуск по таким пунктам:
- одинаковый ли коммит собирается;
- одинаковая ли схема выбирается;
- одинаковый ли Test Plan используется;
- не добавляет ли CI отдельную тестовую цель;
- не запускаются ли XCTest и Swift Testing двумя независимыми командами;
- не делят ли они DerivedData, порт, Keychain или симулятор;
- сохраняется ли полный результат, а не только статус процесса.
Для результатов используйте официальное руководство по запуску тестов и интерпретации результатов. В журнале должны быть связаны тест, фаза выполнения, рабочая область и состояние узла. Если CI сохраняет только строку «test failed», отличить конфликт ресурсов от ошибки приложения будет невозможно.
06Зона ответственности DevOps: чистый удалённый Mac
На удалённом Mac сначала проверьте среду, а затем меняйте код. Нужны отдельные рабочие каталоги для Job, независимый DerivedData и временная директория с идентификатором запуска. Не допускайте, чтобы два задания писали в один файл результатов или использовали один фиксированный порт.
Пошаговая процедура:
- зафиксируйте коммит, Xcode, Scheme и Test Plan;
- создайте чистую копию рабочей области;
- назначьте отдельные каталоги исходников, DerivedData, временных файлов и результатов;
- остановите остаточные процессы предыдущего задания;
- проверьте свободное место, память, активные Runner и фоновые задачи;
- выполните параллельный запуск;
- выполните запуск с локальным
.serialized; - сравните результаты и журналы;
- повторите эксперимент после перезагрузки узла.
Это не означает, что каждый запуск должен начинаться с полного клонирования. Чистая копия нужна как контрольный эксперимент. Если ошибка исчезает только после неё, ищите загрязнение среды, а не сразу переписывайте тесты.
Когда существующий компьютер не позволяет стабильно воспроизвести проблему, отдельный Mac-узел удобнее использовать как выбрасываемый контрольный стенд. В VpsMesh можно рассмотреть аренду удалённого Mac для повторяемого тестового окружения, но узел не заменяет изоляцию кода: он лишь помогает отделить дефект теста от остатков конкретной рабочей станции.
07Решение платформенного руководителя: оставить, ограничить или разделить
Выбор должен зависеть от доказательств, а не от желания быстро получить зелёный CI.
| Вариант | Когда выбирать | Цена решения | Условие пересмотра |
|---|---|---|---|
| Сохранить параллельность | Общие ресурсы удалены, повторные запуски стабильны | Требуется рефакторинг и контроль среды | Повторить проверку после крупных изменений |
Локально применить .serialized |
Конфликт ограничен одной Suite или параметризованной группой | Часть обратной связи станет медленнее | Снять после изоляции ресурса |
| Выделить отдельный Mac | Конфликт связан с UI, симулятором, Keychain или тяжёлой средой | Дополнительный узел и обслуживание | Объединить после доказанной независимости |
| Отключить параллельность проекта | Только как временный аварийный откат | Теряется масштабирование и маскируются дефекты | Назначить владельца и дату повторной проверки |
Сохраняйте три вида доказательств: результатный пакет, журнал команды и состояние узла. Успешный единичный прогон не является приёмкой. Приёмка должна включать чистую рабочую область, повторные запуски, проверку после перезагрузки и возможность связать каждый отказ с тестом, фазой и ресурсом.
Swift Testing может сосуществовать с XCTest, но это не означает, что смешанный запуск автоматически безопасен. Исходный код trait параллельности помогает проверить реализацию и доступные ограничения, однако для проектного решения важнее наблюдаемый результат вашей Scheme и CI-команды.
Как воспроизвести проблему параллельности на удалённом Mac?
Используйте один и тот же коммит и одинаковую конфигурацию, затем сравните отдельный запуск, конкурентную группу, запуск с локальным .serialized и прогон на чистой рабочей области. Между попытками сохраняйте результатный пакет, порядок тестов и состояние узла. Если ошибка появляется только при конкуренции, переходите к карте ресурсов; если она остаётся в отдельном запуске, ищите дефект фикстуры или приложения.
Влияет ли совместное использование Swift Testing и XCTest на параллельность?
Может влиять не сам факт сосуществования, а разные цели, Scheme, Test Plan и команды запуска. Один Runner способен одновременно выполнять обе группы, деля DerivedData, симулятор, порт или Keychain. Поэтому разделяйте фреймворки в журнале CI и проверяйте их фактические границы, а не только список зависимостей проекта.
08Итоговая схема приёмки
Перед восстановлением параллельности отметьте все условия:
- общий объект и глобальное состояние удалены или явно изолированы;
- файлы, порты, UserDefaults и Keychain имеют отдельные пространства;
- UI и симулятор не делят неконтролируемый ресурс;
- Swift Testing и XCTest запускаются по понятным целям;
- Scheme, Test Plan и Xcode совпадают с эталонным запуском;
- DerivedData и временные каталоги не пересекаются;
- результатный пакет сохраняется для каждого задания;
- повторный прогон на чистом узле не меняет диагноз;
- после перезагрузки узла сценарий остаётся воспроизводимым;
- для каждого
.serializedзаписана причина и условие удаления.
Если вы не можете выполнить последний пункт, сериализация пока является не исправлением, а блокировкой симптома.
Постоянный Linux-сервер или локальный компьютер не решит задачу, где нужны Xcode, симулятор и реальные macOS-инструменты. Покупка Mac mini имеет смысл для долгой стабильной нагрузки и физического доступа к устройству, но требует капитальных затрат, обслуживания, резервного питания и самостоятельной изоляции заданий. Общий удалённый узел без разделения рабочих областей, в свою очередь, создаёт конкуренцию за память, файлы и симулятор. Для краткого расследования или миграции рациональнее арендовать у VpsMesh отдельный удалённый Mac, сравнить параллельный и локально сериализованный режимы, а затем оставить аренду только там, где нужны временный CI-стенд и воспроизводимая среда. Варианты аренды можно сопоставить на странице цен удалённого Mac, не выдавая стоимость временного эксперимента за универсальную замену собственной инфраструктуры.
Сначала исправьте границы владения ресурсами. Затем подтвердите результат чистым повторным запуском. И только после этого возвращайте параллельность в удалённый Mac CI.