Чистое тестирование iOS-приложений
Это вторая часть продолжающегося списка статей. Настоятельно рекомендую прочитать первую часть, прежде чем продолжить.
Основы
Тестирование iOS-приложения, созданного с помощью SwiftUI и Combine с использованием чистой архитектуры, можно выполнить в несколько шагов. Во-первых, вам нужно будет создать тестовые примеры для вашего приложения. Это можно сделать с помощью платформы XCTest
, входящей в состав Xcode.
После того, как вы создали свои тестовые примеры, вы можете использовать тип AnyPublisher
для проверки вывода вашего приложения. Тип AnyPublisher
— это издатель, который может создавать выходные данные любого типа, поэтому вы можете использовать его для проверки вывода Publisher
вашего приложения.
Чтобы протестировать Publisher
, который создает значения String
, вы можете использовать метод XCTAssertEqual
для сравнения выходных данных издателя с ожидаемыми выходными данными. Например:
В приведенном выше коде publisher
проверяется путем подписки на него с использованием метода sink
. Когда издатель создает значение, вызывается замыкание receiveValue
, и выходные данные сравниваются с expectedOutput
с помощью метода XCTAssertEqual
. Тест завершится ошибкой, если результат не соответствует ожидаемому результату.
Вы можете использовать этот подход для тестирования любого Publisher
, который производит значения String
и сигнализирует о завершении или неудаче, используя тип Error
. Это может быть полезным способом убедиться, что ваше приложение работает правильно и выдает ожидаемый результат.
Резюме
Это последний результат, который мы получили при построении нашей чистой архитектуры:
Чтобы протестировать это приложение, вы можете использовать следующий подход:
- Напишите модульные тесты для классов доменного уровня, включая класс
MyViewModel
. Эти тесты не должны зависеть от классов на уровнях представления или доступа к данным и должны проверять правильность работы бизнес-логики и обработки данных классаMyViewModel
. - Напишите интеграционные тесты для классов в слоях представления и предметной области. Эти тесты должны создавать экземпляры классов
MyView
иMyViewModel
и проверять, правильно ли они работают вместе, чтобы отображать данные, полученные из уровня доступа к данным. - Напишите интеграционные тесты для классов на уровне доступа к данным. Эти тесты должны создавать экземпляры реализаций протокола
DataFetcher
(например,NetworkDataFetcher
иDatabaseDataFetcher
) и проверять, могут ли они правильно извлекать данные из сети или локальной базы данных.
Тестирование
Каждый из этих предыдущих тестов следует запускать отдельно и писать так, чтобы их можно было запускать и проверять независимо от других тестов. Это гарантирует, что различные уровни приложения будут тестироваться отдельно и тщательно, а любые изменения или дополнения не нарушат существующие тесты.
ViewModel
Например, модульный тест для класса MyViewModel
может выглядеть так:
В предыдущем примере кода создаются тестовые наборы для проверки функциональности класса MyViewModel
. Класс MyViewModel
отвечает за выборку данных из DataFetcher
и делает их доступными для представления.
Класс MyViewModelTests
является подклассом XCTestCase
и содержит единственный тестовый метод под названием testFetchData
. Этот метод используется для проверки метода fetchData
класса MyViewModel
.
Метод testFetchData
создает экземпляр MockDataFetcher
и использует его для инициализации экземпляра MyViewModel
. MockDataFetcher
— это фиктивный объект, который используется вместо реального DataFetcher
в тесте. MockDataFetcher
имеет свойство fetchDataResult
, которое используется для имитации вывода метода fetchData
.
Затем метод testFetchData
устанавливает для свойства fetchDataResult
экземпляра MockDataFetcher
определенное значение и вызывает метод fetchData
для экземпляра MyViewModel
. Это имитирует сценарий, в котором DataFetcher
успешно извлекает данные и возвращает их в MyViewModel
.
После вызова метода fetchData
метод testFetchData
использует метод XCTAssertEqual
для сравнения свойства data
объекта MyViewModel
с ожидаемым значением. Если свойство data
не соответствует ожидаемому значению, тест завершится ошибкой.
Метод testFetchData
также использует XCTestExpectation
для ожидания завершения теста перед завершением. Это необходимо, поскольку метод fetchData
является асинхронным, и тест должен ждать, прежде чем завершит проверку вывода.
Таким образом, тестовый пример гарантирует, что MyViewModel
может правильно получить данные из DataFetcher
и сделать их доступными для представления. Это может помочь убедиться, что приложение работает правильно и выдает ожидаемый результат.
View-ViewModel
Точно так же интеграционный тест для классов MyView
и MyViewModel
может выглядеть так:
Определен класс MockDataFetcher
, который является фиктивной реализацией класса DataFetcher
. Этот фиктивный класс имеет свойство с именем fetchDataResult
, которое используется для имитации результата операции fetchData()
.
Метод fetchData()
класса MockDataFetcher
определен для возврата экземпляра AnyPublisher<String, Error>
. Это означает, что вызывающая сторона может подписаться на публикатора и асинхронно обработать результат операции fetchData()
.
В тестовом случае fetchDataResult
устанавливается в строковое значение, а метод fetchData()
вызывается для экземпляра MyViewModel
. Результирующий экземпляр AnyPublisher<String, Error>
не обрабатывается непосредственно в тестовом примере, но ожидается, что тестируемый экземпляр MyView
подпишется на издателя и обработает результат операции fetchData()
.
Затем тестовый пример проверяет, что метка внутри экземпляра MyView
имеет ожидаемый текст, который указывает, что операция fetchData()
прошла успешно и экземпляр MyView
смог правильно обработать результат операции.
Заключение
Основная цель этих двух статей — разделить слои.
Чтобы разделить слои с помощью чистой архитектуры, SwiftUI и Combine, выполните следующие действия:
- Определите различные уровни в приложении, такие как уровни представления, домена и доступа к данным.
- Вы можете использовать SPM для каждого из идентифицированных слоев. Например, создайте пакет «Представление» для уровня представления, пакет «Домен» для уровня предметной области и пакет «Доступ к данным» для уровня доступа к данным.
- Импортируйте в каждый пакет необходимые фреймворки, такие как SwiftUI и Combine для уровня представления и Core Data или Realm для уровня доступа к данным.
- Определите необходимые типы, протоколы и классы каждого пакета, придерживаясь принципов чистой архитектуры. Например, определите модели представлений, модели и варианты использования на уровне предметной области, а представления и контроллеры представлений на уровне представления.
- Настройте зависимости между пакетами, импортировав нужные пакеты друг в друга. Например, уровень представления может зависеть от уровня предметной области, а уровень предметной области может зависеть от уровня доступа к данным.
- Протестируйте приложение, чтобы убедиться, что слои правильно разделены, а зависимости настроены правильно.
- Постоянно проводите рефакторинг и улучшайте архитектуру по мере роста и развития приложения.
Спасибо за прочтение.