Чтобы в Xcode у каждого сценария были собственный результат и возможность повторного запуска, замените циклический модульный тест XCTest параметризованным @Test из Swift Testing. В этом руководстве на примере локального расчёта раскладки плиток показан переход и проверено, как он влияет на отображение результатов.
Почему цикл XCTest не показывает результат каждого сценария отдельно?
XCTest обнаруживает методы тестирования в подклассе XCTestCase. Эти методы не принимают параметров, а их имена начинаются с test. Цикл внутри обнаруженного метода остаётся частью этого метода: его итерации не становятся отдельными обнаруженными тестами. Имя сценария в проверке может сделать диагностику понятнее, но не создаёт отдельный элемент управления запуском. См. документацию Apple Defining Test Cases and Test Methods.
Жизненный цикл подчиняется той же границе. XCTest выполняет методы настройки экземпляра перед каждым методом тестирования, а методы очистки — после него. Поэтому цикл из пяти итераций не получает автоматически пять циклов настройки и очистки. Если итерация изменяет тестовую среду, следующая может унаследовать это состояние, если тест явно его не сбросит. Apple описывает эту последовательность в разделе Set Up and Tear Down State in Your Tests.
Swift Testing предлагает нужный результат: одна функция принимает аргументы сценариев, а среда выполнения рассматривает её вызовы как независимые случаи. Xcode показывает аргументы и позволяет запускать отдельные случаи. Apple демонстрирует такое поведение в разделе Go further with Swift Testing.
Это различие важно при диагностике сбоев. Ошибка проверки и досрочный выход — разные события: после обычной ошибки проверки цикл может продолжить проверку, тогда как выброшенная ошибка или явный return не позволит выполнить следующие итерации. Если разделить сценарии на случаи, между ними не будет общего потока управления. Однако это не позволяет восстановиться после сбоя, завершающего процесс.
Подготовьте существующую цель модульных тестов
Для этого процесса используйте Xcode 16 или более поздней версии. Swift Testing входит в эти дистрибутивы Xcode и в цепочки инструментов Swift.org, начиная со Swift 6.0. Проект Swift рекомендует для обычного тестирования использовать встроенную версию; этому примеру не нужна внешняя зависимость пакета. См. раздел Obtaining Swift Testing.
Сохраните существующий пакет модульных тестов. Apple прямо поддерживает Swift Testing и XCTest в одной цели, поэтому постепенный переход можно начать с одного нового файла Swift. В Xcode выберите File > New > New File From Template, затем Swift Testing Unit Test и добавьте файл в существующую цель модульных тестов. Если в проекте нет цели тестов, создайте Unit Testing Bundle через File > New > Target. Эти действия описаны в разделе Adding tests to your Xcode project.
В этом руководстве тестируется непосредственно код вычислений. Автоматизация окна рабочего стола здесь не рассматривается. Документированный Apple способ автоматизации интерфейса по-прежнему использует XCTest; этот параметризованный подход подходит для тестирования базовой логики, определяющей раскладку. Границу между фреймворками также описывает руководство по добавлению тестов.
Представьте каждый сценарий полной записью аргументов
Предположим, приложение для Mac располагает плитки по строкам. При четырёх столбцах ноль плиток требует ноль строк, четыре плитки заполняют одну строку, а для пяти плиток нужны две строки. Задача — сохранить эти проверки и сделать каждый сценарий отдельным, видимым и запускаемым.
Используйте одну запись с названием сценария, входными данными и ожидаемым результатом. Так ожидаемый результат останется связан с входными данными при добавлении или перестановке случаев. Следующие пять записей — пример тестовых данных, а не результаты наблюдений за существующим приложением.
| Сценарий | Число плиток | Столбцы | Ожидаемое число строк | Назначение |
|---|---|---|---|---|
empty |
0 | 4 | 0 | Пустой ввод |
one-tile |
1 | 4 | 1 | Неполная первая строка |
exact-row |
4 | 4 | 1 | Точное совпадение с границей |
wrap |
5 | 4 | 2 | Первая плитка за границей |
two-full-rows |
8 | 4 | 2 | Точное кратное |
Поместите этот полный автономный файл в цель модульных тестов. В нём содержится синтетическая реализация, поэтому его можно попробовать без модуля приложения и других файлов. В нём не используются службы, моки, файлы или сетевой доступ.
import Testing
private struct TileLayout {
func rowCount(tileCount: Int, columns: Int) -> Int {
precondition(tileCount >= 0)
precondition(columns > 0)
let fullRows = tileCount / columns
let partialRow = tileCount % columns == 0 ? 0 : 1
return fullRows + partialRow
}
}
private struct LayoutScenario:
Codable, Sendable, CustomTestStringConvertible {
let name: String
let tileCount: Int
let columns: Int
let expectedRows: Int
var testDescription: String { name }
static let examples: [LayoutScenario] = [
.init(name: "empty", tileCount: 0,
columns: 4, expectedRows: 0),
.init(name: "one-tile", tileCount: 1,
columns: 4, expectedRows: 1),
.init(name: "exact-row", tileCount: 4,
columns: 4, expectedRows: 1),
.init(name: "wrap", tileCount: 5,
columns: 4, expectedRows: 2),
.init(name: "two-full-rows", tileCount: 8,
columns: 4, expectedRows: 2)
]
}
struct TileLayoutTests {
@Test("Tile rows", arguments: LayoutScenario.examples)
fileprivate func rowCountMatchesScenario(
_ scenario: LayoutScenario
) {
let layout = TileLayout()
let actualRows = layout.rowCount(
tileCount: scenario.tileCount,
columns: scenario.columns
)
#expect(actualRows == scenario.expectedRows)
}
}
При переходе цикл переносится в @Test(arguments:): каждый элемент становится аргументом отдельного вызова. Codable обеспечивает поддержку кодирования для выборочных повторных запусков, а CustomTestStringConvertible задаёт понятную метку сценария. Apple описывает параметризацию в разделе Implementing parameterized tests, а пользовательские описания аргументов — в разделе Go further with Swift Testing.
Тестовая среда намеренно создаётся внутри функции теста. Каждый вызов создаёт собственный TileLayout; результаты не зависят от предыдущего сценария. При переносе существующей тестовой среды продуманно перенесите и её настройку, а не рассчитывайте на унаследованные хуки XCTest. Наборы Swift Testing не наследуются от XCTestCase; Apple предлагает перенести настройку в инициализатор, а очистку класса или актора — в deinit, как описано в разделе Migrating a test from XCTest.
Расчёт принимает неотрицательное число плиток и строго положительное число столбцов. Предусловия задают этот контракт; недопустимые значения не входят в случаи этого примера. Если реальный код приложения должен возвращать восстанавливаемую ошибку при недопустимом вводе, измените контракт API и добавьте соответствующие тесты, прежде чем расширять эту таблицу.
Проверьте раздельные результаты и повторно запустите один неудачный случай
Запустите параметризованную функцию с помощью значка ромба рядом с её объявлением. Затем откройте Test navigator и разверните функцию, чтобы просмотреть её случаи. Чтобы запустить один случай, наведите указатель на его ромб и нажмите кнопку воспроизведения. Кнопка воспроизведения у родительской функции запускает все её случаи. Apple описывает оба действия в разделе Running tests and interpreting results.
Чтобы контролируемо проверить отображение результатов, временно измените значение expectedRows в записи wrap с 2 на 3. Запустите всю параметризованную функцию. Ожидается выполнение пяти случаев; сравнение wrap завершится ошибкой, поскольку реализация вернёт 2. Выберите этот случай и запустите его отдельно, затем восстановите ожидаемое значение 2. Это намеренное изменение тестовых данных, а не свидетельство дефекта реализации.
Изучите сведения Results и Arguments в неудавшейся проверке ожидаемого значения, чтобы сопоставить вычисленный результат со входными данными сценария. Для полного запуска откройте View > Navigators > Reports, выберите действие теста и просмотрите раздел Tests. Элементы управления для проверки результатов описаны в руководстве Apple по результатам тестирования.
Для выборочного запуска помимо понятных меток есть ещё одно требование: среда выполнения должна детерминированно сопоставлять каждый аргумент. Согласно актуальной документации Apple, для этого подходят CustomTestArgumentEncodable, некоторые типы RawRepresentable, Encodable или Identifiable с идентификатором, допускающим кодирование. Запись примера, соответствующая Codable, удовлетворяет требованию кодирования. Одной пользовательской строки для отображения недостаточно. См. раздел Implementing parameterized tests.
Ожидаемую арифметику для всех пяти записей локально проверили с помощью целочисленного деления и остатка в Python. Пример неправильной реализации, использующей только деление с округлением вниз, не проходит проверки one-tile и wrap. Файл Swift просмотрели, но не компилировали в этой исследовательской среде, поскольку Swift был недоступен; запуск в Xcode выше остаётся этапом исполняемой проверки.
Сохраните нужное число случаев и жизненный цикл
При переходе полезно сравнить три вещи: число записей сценариев, число случаев, выполненных при полном запуске, и возможность отдельно запустить выбранный случай. Для этого примера предполагается пять случаев. Если число другое, прежде чем менять вычисление, проверьте формирование аргументов и выбор тестов.
Храните связанные ожидаемые значения вместе. Если передать две отдельные коллекции в arguments:, будет создано декартово произведение. Поэтому пять входных данных и пять ожидаемых результатов дадут 25 комбинаций вместо пяти сопоставленных сценариев. Отдельные коллекции подходят, когда важны все сочетания; для связанных данных используйте записи или zip(). Apple объясняет это различие в разделе Implementing parameterized tests.
Если используете zip(), перед созданием пар проверьте длину коллекций: Swift останавливается на более короткой последовательности. В противном случае лишний ожидаемый результат может остаться неиспользованным, и для него не будет создан случай. Одна запись сценария позволяет избежать именно такого несоответствия. Поведение усечения описано в справочнике Swift по функции zip(::).
При переходе также нужно учитывать порядок выполнения. По умолчанию Swift Testing запускает случаи параллельно. В этом примере используются неизменяемые аргументы и локальная тестовая среда, поэтому результаты не зависят от порядка. Если для параметризованной функции временно требуется последовательное выполнение, добавьте .serialized в атрибут @Test. Применение этого трейта к набору также упорядочивает входящие в него тесты и случаи, но не сериализует другие наборы. См. раздел Running tests serially or in parallel.
Последовательный запуск не создаёт чистое состояние для каждого случая и не устанавливает контракт зависимостей между сценариями. Если случай проходит только после того, как другой случай что-то изменил, перестройте его организацию так, чтобы он выполнялся независимо. Если несколько шагов намеренно составляют одну непрерывную операцию, оставьте их в одном тесте и параметризуйте целые операции.
В этом примере после перехода используйте только проверки Swift Testing. При повторном использовании вспомогательных функций с проверками XCTest уточните совместимость с вашей цепочкой инструментов: в актуальном руководстве Apple по переходу описаны режимы и настройки по умолчанию, различающиеся в зависимости от цепочки инструментов и версии Swift Tools. Если весь файл использует только Testing, эта зависимость не нужна. См. раздел Migrating a test from XCTest.
Если переход пока невозможен, поддерживаемая альтернатива в XCTest — создать отдельный именованный метод тестирования без параметров для каждого сценария и при необходимости использовать общие вспомогательные функции для вычислений и проверок. Apple рекомендует тест для каждого сценария в руководстве по добавлению тестов. Так отдельные результаты методов сохраняются без параметризации Swift Testing.
Переход завершён, когда каждому исходному сценарию соответствует выполненный тестовый случай, тестовая среда начинается с нужного состояния, а выбранный случай можно успешно запустить отдельно. При этом сохраните граничные входные данные и ожидаемые результаты: раздельное отображение полезно тем, что позволяет проще находить, диагностировать и повторять исходные проверки.
Источники
- Defining Test Cases and Test Methods — Apple
- Set Up and Tear Down State in Your Tests — Apple
- Go further with Swift Testing — Apple
- Obtaining Swift Testing — проект Swift
- Adding tests to your Xcode project — Apple
- Implementing parameterized tests — Apple
- Migrating a test from XCTest — Apple
- Running tests and interpreting results — Apple
- zip(::) — Apple
- Running tests serially or in parallel — Apple