@Suite
Глава про макрос @Suite разделена на 4 сценария:
- Объединение в группу
- Настройка отображаемого имени
- Поддержка методов
- Ограничения и тонкости использования
Объединение в группу
Что приходит на ум при работе с большим количеством функций?
Правильно, возможность упорядочить их с помощью чего-либо, а ещё лучше объединить их в один тип данных.
Макрос @Suite
идеально подходит не только для объединения в один тип данных, а так же для использования трейтов.
Вообще, такая группировка возможна в 2ух сценариях:
- С помощью пользовательских типов данных
- Применяя атрибут @Suite для пользовательских типов данных
К таким типам данных относят: struct
, class
, actor
и enum
.
@Suite
struct SingleProfile {...}
@Suite
class SharedService {...}
@Suite
actor DatabaseProvider {...}
@Suite
enum SwaggerAPI {...}
tip
@Suite
не является обязательным атрибутом для применения, но позволяет использовать трейты, подобные в макросе @Test
, для расширения функционала.
Атрибут @Suite
не является обязательным для распознавания тестовых функций, содержащихся в типе, но его использование позволяет настраивать отображение типа данных в IDE и в командной строке. Если к типу данных применён трейт, такой как .tags(...)
или .disabled(...)
, он автоматически наследуется всеми тестами, содержащимися в этом типе данных.
Пользовательские типы данных могут содержать не только функции для тестирования, но и другие методы. Также они могут содержать вложенные типы данных. Чтобы добавить вложенный тип данных, нужно объявить дополнительный тип внутри основного.
По умолчанию тесты в рамках одного типа данных выполняются параллельно. Подробнее о параллелизации можно узнать в разделе Конкурентный и последовательный запуск тестов.
Настройка имени данным
Чтобы задать имя для типа данных, передайте строковый литерал в качестве аргумента для атрибута @Suite:
@Suite("Food truck tests")
struct FoodTruckTests {
@Test func foodTruckExists() {
// ... логика
}
}
Для дальнейшей настройки внешнего вида и поведения метода можно использовать трейты, такие как .tags(...)
.
Методы для тестирования в Suite
Если метод для тестирования объявлен как метод экземпляра (без использования ключевых слов static или class), библиотека тестирования вызывает этот обычный метод (без атрибута @Test), а затем вызывает метод на этом экземпляре. Например:
@Suite
struct ColdWeather {
@Test
func dressWarm() {
#expect(-10 + -3 == -13)
}
}
Эквивалентно следующему:
@Suite
struct ColdWeather {
func dressWarm() {
#expect(-10 + -3 == -13)
}
@Test
static func someStaticFn() {
let instance = ColdWeather()
instance.dressWarm()
}
}
В консоли Xcode ты увидишь вывод:
◇ Suite ColdWeather started.
◇ Test someStaticFn() started.
✔ Test someStaticFn() passed after 0.001 seconds.
✔ Suite ColdWeather passed after 0.001 seconds.
✔ Test run with 1 test passed after 0.001 seconds.
Что здесь происходит?
- Ты реализовал метод
dressWarm()
в качестве обычного метода, но использовал макрос сравнения#expect(...)
- В методе
static someStaticFn()
применил атрибут@Test
, создал инстанс типа данныхColdWeather()
и только затем вызвал методdressWarm()
Как видно выше, статический метод завершился успехом, поскольку выражение-10 + -3 == -13
является верным.
А что будет, если временно отключить статический метод и запустить тест для типа данных?
@Suite
struct ColdWeather {
func dressWarm() {
#expect(-10 + -3 == -13)
}
@Test(.disabled())
static func someStaticFn() {
let instance = ColdWeather()
instance.dressWarm()
}
}
В таком случае запуск тестов для ColdWeather
осуществится без ошибок, но и тестировать попросту будет нечего. Это не является ошибкой, это особенность данного фреймворка:
◇ Suite ColdWeather started.
✘ Test someStaticFn() skipped.
✔ Suite ColdWeather passed after 0.001 seconds.
✔ Test run with 1 test passed after 0.001 seconds.
Ограничения для типов Suite
При использовании типов данных вместе с атрибутом @Suite накладываются дополнительные ограничения:
- Необходимость наличия инициализатора
Если тип содержит методы для тестирования, он должен быть инициализирован без свойств принимающих значения. Инициализатор может быть:
- явным или неявным;
- синхронным или асинхронным;
- бросающим исключения (
init() throws
); - любого уровня доступа (
private
,internal
,public
).
Пример:
@Suite
struct FoodTruckTests {
var batteryLevel = 100
@Test
func foodTruckExists() {...} // ✅ OK: есть неявный инициализатор.
}
@Suite
struct CashRegisterTests {
private init(cashOnHand: Decimal = 0.0) async throws {...}
@Test
func calculateSalesTax() {...} // ✅ OK: доступен явный инициализатор.
}
struct MenuTests {
var foods: [Food]
var prices: [Food: Decimal]
@Test
static func specialOfTheDay() {...} // ✅ OK: функция статическая.
@Test
func orderAllFoods() {...} // ❌ Ошибка: типу требуется инициализатор.
}
Компилятор выдаст ошибку, если тип данных не удовлетворяет этому требованию.
Атрибут @available
можно применять к функции или методу, чтобы ограничить её доступность во время выполнения. Однако типы данных, включая вложенные, не могут быть аннотированы этим атрибутом:
@Suite
struct FoodTruckTests {...} // ✅ OK: тип всегда доступен.
@available(macOS 11.0, *) // ❌ Ошибка: тип данных должен быть всегда доступен.
@Suite
struct CashRegisterTests {...}
@available(macOS 11.0, *)
struct MenuItemTests { // ❌ Ошибка: вложенный тип должен быть доступен.
@Suite
struct BurgerTests {...}
}
Компилятор выдаст ошибку, если тип данных нарушает это правило.
Атрибут @available(...) используется для обозначения доступности типа данных или функции.