EXPEDIA GROUP ТЕХНОЛОДЖИ — ИНЖИНИРИНГ

Асинхронное тестирование с планировщиками Combine

Тестирование асинхронного кода может быть сложным, но использование фреймворка Combine с правильными инструментами может облегчить его.

Во-первых, давайте погрузимся в пример кода. Это в Swift, и это упрощенная версия кода в приложении Expedia в Apple App Store.

Это частная функция, выполняющая сетевые операции с использованием издателя Combine. Функция подписывается на этого издателя в очереди .userInteractive и использует оператор receive для получения вывода в основном потоке для работы пользовательского интерфейса.

Когда дело доходит до тестирования, у нас есть следующий макет.

Мы используем издатель Just, чтобы передать вывод, который мы хотим использовать в тесте, и избежать попадания в сеть. Текущее решение для тестирования:

Мы ждем 0,3 секунды (произвольное время), чтобы дать циклу выполнения возможность выполнить полный тик. Надеюсь, макет будет готов, чтобы проверить это. Однако в тот момент, когда мы используем операторы подписки и получения, мы делаем издателя асинхронным и трудным для тестирования.

Вот где тест становится ненадежным. 0,3 секунды может быть достаточно на наших машинах в большинстве случаев, но не в CI. Кроме того, мы можем связать несколько издателей, переключающихся между фоновым и основным потоком, что сделает время ожидания еще менее предсказуемым. По сути, существует несоответствие между синхронным тестовым кодом и асинхронной операцией.

Решение

Платформа Combine предоставляет протокол планировщика, мощную абстракцию для описания того, как и когда будут выполняться единицы работы. Он объединяет множество разрозненных способов выполнения работы, таких как DispatchQueue, RunLoop и OperationQueue. Это позволяет нам преобразовать асинхронный код в синхронный, изменив планировщик.

Scheduler — это протокол, и Apple не предлагает стертый тип для планировщиков, как для AnyPublisher или AnyCancellable. Таким образом, нам пришлось бы внедрять Scheduler в каждое место, используя Generics.
Однако есть лучший способ использовать стертый шрифт для планировщиков. Наличие AnyScheduler позволит нам работать так же, как мы работаем с Publishers.

Чтобы не изобретать велосипед, есть хорошая реализация AnyScheduler от Point Free (https://github.com/pointfreeco/combine-schedulers). Они также предлагают больше типов планировщиков, которые могут быть полезны во время тестирования.

Как мы тестируем этот код?

Мы тестируем его точно так же, как любой другой синхронный код, продолжая тот же пример:

Мы создаем зависимости, заглушаем методы и проверяем вывод точно так же, как и в любом другом синхронном тесте. Мы удалили исключения, которые не будут выполняться, так что больше никаких ожидающих вызовов, тесты теперь выполняются быстрее, и мы можем попрощаться с нестабильностью.

Но.. Какие изменения нам нужно внести?

Во-первых, мы импортируем CombineSchedulers. Далее мы объявляем два планировщика, которые используем для фонового и основного потоков. Наконец, мы предоставляем значения по умолчанию, чтобы избежать поломки фабрик в файле init.

Наша функция остается прежней, за исключением новых экземпляров планировщика.

Для тестирования мы используем непосредственный Планировщик для `work` и `main`, таким образом конвертируя издатель в синхронный.

Важно помнить, что ImmediateScheduler не будет эффективен при использовании других операторов Combine, таких как debounce, Throttle, Delay, Timeout и т. д. Нам нужно использовать TestScheduler для управления выполнением детерминированным образом.

Объединить анализ библиотеки планировщика

Обзор

Размеры

  • Всего строк кода: 2239
  • Исходные строки кода: 1985 г.
  • Самый длинный файл: Timer.swift (311 строк исходного кода)
  • Самый длинный тип: AnyScheduler (339 исходных строк)

Структура

Импорт:

  • Комбинат (14)
  • Фонд (7)
  • XCTest (6)
  • КомбинатПланировщики (5)
  • SwiftUI (2)
  • XCTestDynamicOverlay (2)
  • Описание пакета (2)
  • XCTest.XCTFail (1)
  • Дарвин (1)
  • Отправка (1)
  • Фонд (1)

Контроллеры просмотра UIKit: 0

Просмотров: 0

Просмотры SwiftUI: 0

Внешняя зависимость: XCTestDynamicOverlay