Чтобы Xcode выделял тест, вызвавший общую вспомогательную функцию XCTest, задайте ей параметры #filePath и #line со значениями по умолчанию и передавайте оба параметра в проверки. В этом пошаговом руководстве показаны исправление, вложенные вспомогательные функции и эквивалент на Swift Testing.
Почему Xcode выделяет вспомогательную функцию?
Общая проверка добавляет еще одно место в исходном коде. Тест вызывает вспомогательную функцию, а та — проверку XCTest. Если проверка использует аргументы местоположения по умолчанию, сообщаемым местом будет вызов проверки внутри вспомогательной функции. Apple документирует параметры file: StaticString = #filePath и line: UInt = #line как для XCTAssertEqual, так и для XCTFail.
Сохраните местоположение вызывающего кода на границе вспомогательной функции, а затем передайте эти значения дальше. Значения местоположения исходного кода Swift по умолчанию фиксируют место вызова; Swift Project описывает это поведение в документе SE-0274: краткие имена файловых магических значений. Apple также показывает этот точный шаблон вспомогательной функции XCTest в руководстве Переход на Swift Testing, демонстрируя, как Xcode связывает сбой с вызовом вспомогательной функции.
Для разработчика, работающего в Xcode на Mac, задача состоит в том, чтобы сохранить повторно используемые проверки и при этом обеспечить переход от каждой ошибки к соответствующим входным данным теста. В приведенных ниже примерах используются только небольшие массивы целых чисел. При их запуске не нужны модель приложения, подмена сервиса, загруженная зависимость или доступ к сети.
Добавьте в вспомогательную функцию XCTest параметры местоположения вызывающего кода
Используйте существующую цель модульного тестирования macOS. Если в проекте ее нет, выберите File > New > Target, отфильтруйте по Test и выберите Unit Testing Bundle. Чтобы добавить файл теста, выберите File > New > New File From Template и укажите XCTest Unit Test. Эти пути документированы Apple в руководстве Добавление тестов в проект Xcode.
Поместите этот полный файл теста в цель модульного тестирования. Во втором тесте намеренно указана неверная ожидаемая сумма, чтобы вы могли проверить местоположение ошибки.
import XCTest
private func assertTotal(
_ values: [Int],
equals expected: Int,
file: StaticString = #filePath,
line: UInt = #line
) {
let actual = values.reduce(0, +)
XCTAssertEqual(
actual,
expected,
"Unexpected total for \(values)",
file: file,
line: line
)
}
final class TotalHelperTests: XCTestCase {
func testMatchingTotal() {
assertTotal([2, 3, 4], equals: 9)
}
func testDeliberatelyIncorrectTotal() {
assertTotal([2, 3, 4], equals: 10)
}
}
Важны типы параметров, значения по умолчанию и явная передача аргументов. StaticString и UInt соответствуют сигнатуре проверки XCTest. Оставьте #filePath и #line непосредственно в значениях параметров вспомогательной функции по умолчанию. Тогда тест вызывает вспомогательную функцию, не указывая вручную ни один из аргументов местоположения. См. документированные параметры XCTAssertEqual.
Пример с арифметикой иллюстративный: 2 + 3 + 4 равно 9, поэтому первое сравнение совпадает, а второе — нет. Эти арифметические примеры были проверены локально с помощью Python. В исследовательской среде не было доступно исполняемого файла Swift, поэтому примеры Swift были изучены, но здесь не компилировались и не запускались.
Проверьте местоположение и результат теста
Выберите Product > Test или запустите отдельный тест с помощью кнопки запуска рядом с его объявлением. После этого выберите упавший тест в Test navigator и изучите диагностику. Apple описывает эти элементы управления и сведения об ошибках в руководстве Запуск тестов и интерпретация результатов.
В этом примере проверьте два отдельных результата: testDeliberatelyIncorrectTotal() завершается с ошибкой, а диагностика проверки указывает на вызов assertTotal([2, 3, 4], equals: 10). Ожидаемая привязка к вызывающему коду соответствует показанному Apple шаблону передачи параметров. Проверив ее, измените 10 на 9 и запустите тесты снова; намеренное несоответствие следует удалить из окончательного набора тестов.
Если диагностика по-прежнему указывает на место внутри вспомогательной функции, проверьте итоговый вызов проверки. Одного объявления параметров местоположения недостаточно: в месте регистрации сбоя вспомогательная функция должна передавать file: file и line: line.
Сохраняйте местоположение на каждом уровне вложенных вспомогательных функций
Для вложенных вспомогательных функций нужна такая же явная передача параметров. Предположим, тест вызывает вспомогательную функцию предметной области, а та — низкоуровневую вспомогательную функцию проверки. Если внешняя функция не передаст аргументы местоположения, низкоуровневая функция зафиксирует место промежуточного вызова.
Это граница языка, которую важно проверить: значения местоположения исходного кода по умолчанию не передаются автоматически при произвольных вызовах вспомогательных функций. В документе SE-0285: упрощение перехода на краткие магические строки файлов явно обсуждается непереходящий характер этих значений по умолчанию.
Ниже приведен полный альтернативный файл теста. В нем показано, как вспомогательная функция вызывает другую вспомогательную функцию, сохраняя исходное местоположение теста.
import XCTest
private func assertIntegerEqual(
_ actual: Int,
_ expected: Int,
file: StaticString = #filePath,
line: UInt = #line
) {
XCTAssertEqual(actual, expected, file: file, line: line)
}
private func assertRowTotal(
_ row: [Int],
equals expected: Int,
file: StaticString = #filePath,
line: UInt = #line
) {
assertIntegerEqual(
row.reduce(0, +),
expected,
file: file,
line: line
)
}
final class NestedTotalHelperTests: XCTestCase {
func testRowTotal() {
assertRowTotal([2, 3, 4], equals: 9)
}
}
При проверке более длинной цепочки вспомогательных функций можно проследить путь вызова: начните с вызова теста, пройдите по каждой функции и убедитесь, что одно и то же зафиксированное местоположение доходит до проверки. Зафиксируйте значения по умолчанию на границе, которая должна отображаться в результате; через остальные уровни передавайте уже имеющиеся значения. Не скрывайте фиксацию за другой функцией, используемой как аргумент по умолчанию, — это ловушка, описанная в SE-0285.
Останавливает ли регистрация ошибки вызывающий тест?
Привязка ошибки и управление потоком — отдельные решения. Для XCTest параметр continueAfterFailure по умолчанию равен true, поэтому после неудачной проверки выполнение обычно продолжается. Изменение сообщаемого файла и строки не меняет это свойство. Apple документирует такое поведение в continueAfterFailure.
Если вспомогательная функция обнаруживает условие, при котором оставшаяся работа внутри нее теряет смысл, предусмотрите явный выход из нее. Например, зарегистрируйте ошибку с переданным местоположением, а затем выполните возврат из вспомогательной функции. Помните, что возврат из вспомогательной функции передает управление обратно вызывающему коду; спроектируйте последующие действия вызывающего кода соответствующим образом. В руководстве по миграции Apple показывает безусловную регистрацию ошибки с последующим возвратом.
Первую проверку оставьте синхронной и небольшой. Сначала убедитесь, что намеренно неверный расчет приводит к ошибке в ожидаемом месте вызова, и только после этого добавляйте более сложную подготовку или асинхронное поведение. Такая последовательность помогает отличить проблему с передачей местоположения от других проблем выполнения тестов.
Используйте поддерживаемое местоположение исходного кода в Swift Testing
Для вспомогательной функции, вызываемой тестами Swift Testing, используйте #expect с явным значением SourceLocation. Apple описывает sourceLocation как место, к которому привязываются ожидания и проблемы, и указывает, что ложное условие регистрирует проблему для теста, выполняющегося в текущей задаче. См. expect(_:_:sourceLocation:).
Этот полный альтернативный файл требует Swift Testing, доступного в Xcode 16 и Swift 6.0. Он фиксирует четыре компонента местоположения как отдельные значения по умолчанию, а затем создает документированный объект с помощью инициализатора SourceLocation.
import Testing
private func expectTotal(
_ values: [Int],
equals expected: Int,
fileID: String = #fileID,
filePath: String = #filePath,
line: Int = #line,
column: Int = #column
) {
let location = SourceLocation(
fileID: fileID,
filePath: filePath,
line: line,
column: column
)
let actual = values.reduce(0, +)
#expect(actual == expected, sourceLocation: location)
}
@Test func matchingTotal() {
expectTotal([2, 3, 4], equals: 9)
}
@Test func deliberatelyIncorrectTotal() {
expectTotal([2, 3, 4], equals: 10)
}
Инициализатор требует правильно отформатированный непустой идентификатор файла, а также положительные номера строки и столбца. Фиксация компонентов, предоставленных компилятором, позволяет не придумывать эти значения. При вложении таких вспомогательных функций передавайте все четыре зафиксированных аргумента, как в версии для XCTest передаются два аргумента. И здесь после проверки привязки исправьте намеренное ожидание 10.
Что меняется, когда XCTest и Swift Testing используют одну вспомогательную функцию?
Оба фреймворка могут сосуществовать в одном пакете тестов, но для вспомогательной функции, пересекающей границы фреймворков, нужна обработка с учетом версии. В актуальном руководстве по миграции Apple описывает взаимодействие фреймворков и значения по умолчанию: цепочки инструментов до Swift 6.4 используют none; в Swift 6.4 и более поздних версиях для пакетов, в которых объявлена более ранняя версия инструментов, по умолчанию используется limited, а для пакетов с версией инструментов 6.4 или выше — complete.
| Режим взаимодействия | Ошибка проверки XCTest внутри теста Swift Testing |
|---|---|
none |
Межбиблиотечная проблема игнорируется |
limited |
Сообщается как предупреждение с рекомендацией модернизировать код |
complete |
Сохраняет серьезность ошибки с рекомендацией модернизировать код |
strict |
Сообщается с помощью fatalError |
Эти режимы объясняют, почему вспомогательная функция может сообщить правильный текст ошибки, но тест Swift Testing при этом пройдет. На сессии WWDC26 о миграции Apple показывает именно такой результат, а затем меняет параметр взаимодействия в плане тестирования на Complete в разделе Test Execution, чтобы сохранить серьезность ошибки. Прежде чем полагаться на эту возможность, проверьте установленную цепочку инструментов.
В более старых цепочках инструментов регистрируйте проверки средствами соответствующего фреймворка, а общий детерминированный расчет вынесите в отдельный код. Для поддерживаемых конфигураций взаимодействия Apple также демонстрирует замену XCTFail во вспомогательной функции на Issue.record из Swift Testing. При переходе все равно нужно сохранять передачу местоположения исходного кода.
Диагностика оставшихся проблем
| Наблюдение | Что проверить дальше |
|---|---|
| Ошибка указывает на место внутри вспомогательной функции проверки | Убедитесь, что проверке передаются зафиксированные аргументы местоположения |
| Ошибка указывает на место внутри внешней вспомогательной функции | Убедитесь, что внешняя функция передает местоположение своего вызывающего кода |
| Тест Swift Testing проходит, несмотря на проверку XCTest | Проверьте поддержку в цепочке инструментов и режим взаимодействия |
| После ошибки проверки XCTest код продолжает выполняться | Проверьте ожидаемое управление потоком и continueAfterFailure |
| Тест не запускается в наборе примеров | Убедитесь, что файл входит в цель модульного тестирования, а активный план тестирования включает эту цель |
Настройку обнаружения и выбора тестов описывают руководство Apple по настройке тестов и выполнению тестов. При проверке взаимодействия фреймворков прежде чем менять аргументы местоположения, ознакомьтесь с приведенными выше документированными правилами.
Полезная общая вспомогательная функция сохраняет три свойства: осмысленное сравнение, диагностику, привязанную к нужному вызывающему коду, и ожидаемый результат теста. Проверьте их с одним намеренно неверным значением, удалите это несоответствие и продолжайте передавать зафиксированное местоположение при добавлении каждого нового уровня вспомогательных функций.
Источники
- XCTAssertEqual(::_:file:line:) — Apple
- XCTFail(_:file:line:) — Apple
- Добавление тестов в проект Xcode — Apple
- Запуск тестов и интерпретация результатов — Apple
- continueAfterFailure — Apple
- Миграция теста с XCTest — Apple
- expect(::sourceLocation:) — Apple
- init(fileID:filePath:line:column:) — Apple
- Переход на Swift Testing — Apple
- Краткие имена файловых магических значений — Swift Project
- Упрощение перехода на краткие магические строки файлов — Swift Project