135. Тестируем парсинг моделей
Недавно я выпустил в аппстор приложение для тренировок, предварительно провел много ручных тестов и написал больше тысячи автотестов. В этой статье расскажу про важность забытых мной тестов 😅
Тесты в первом релизе
Вот обобщенный перечень тестов, которые были на момент первого релиза:
- Тесты для экрана выполнения тренировки (настройка, завершение шагов, получение результата, работа с таймером, турбо-дни и т.д.)
- Тесты для экрана превью тренировки (построение активности дня, редактирование комментариев, обновление данных, сохранение тренировки и т.д.)
- Тесты для управления статусом программы (получение статуса, запуск, сброс, синхронизация, журнал синхронизации, интеграционные тесты и т.д.)
- Тесты для работы с дневником тренировок (базовые операции, комментарии, разрешение конфликтов, обработка ошибок, тренировки и т.д.)
- Тесты для работы с инфопостами (синхронизация, парсинг, управление ресурсами, работа с YouTube, избранное и т.д.)
- Тесты для работы с прогрессом пользователя (базовые операции, синхронизация, работа с фотографиями, калькулятор прогресса и т.д.)
- Тесты для работы с пользовательскими упражнениями
- Тесты для настроек приложения
- Тесты для воспроизведения звука
- Тесты для обновления стран
- Тесты для обработки изображений
- Тесты для управления ресурсами изображений
- Тесты для работы с YouTube видео
- Тесты для моделей тренировок
- Тесты для моделей дней
- Тесты для моделей прогресса
- Тесты для моделей пользователя
- Тесты для моделей синхронизации
- Тесты для других моделей (HomeScreenModel, ShareAppURL, TimerSound)
- Тесты для создания программы тренировок (инициализация дня, работа с активностью дня, турбо-дни, пользовательские упражнения, типы выполнения и т.д.)
У меня была цель протестировать всю возможную логику, чтобы было проще поддерживать и развивать приложение в любое время: хоть сейчас, хоть через год, когда я скорее всего забуду большинство мелочей, влияющих на ту же синхронизацию данных с сервером и т.д.
Проблема первого релиза
Про релиз сделали пост на сайте, с которым синхронизируется приложение, и в их телеграм-чате - там я случайно увидел пост одного из пользователей:
Моя реакция, когда я увидел этот пост: 🤯🤦♂️
Для разработчика проблема очевидна: не получилось спарсить (обработать) какой-то из ответов сервера, а судя по скриншоту, это вообще происходит при первой авторизации, т.к. на главном экране ничего нет кроме алерта.
Это был единственный момент, который я не протестировал: зарегистрировать нового пользователя на сайте, а потом авторизоваться в iOS-приложении.
Т.е. я тестировал только существующих пользователей, и для них все работает корректно. Вот это поворот 🙂
С пользователем мне повезло, он прислал мне в личку детали по воспроизведению проблемы и я сходу смог у себя это повторить.
Причина проблемы
Проблема возникала при декодировании JSON ответа от сервера в структуру CurrentRunResponse.
Я использовал стандартные методы декодирования, которые не могли корректно обработать поле с датой: метод decodeIfPresent не мог правильно обработать случай, когда сервер возвращает null в JSON в поле с датой.
При этом использовалась стратегия декодирования даты iso8601, которая не поддерживала формат с дробными секундами, который иногда возвращал сервер.
Почему только для новых пользователей
Проблема возникала только для новых пользователей, которые еще не начинали тренироваться на сайте/в приложении, потому что сервер возвращал разные данные в зависимости от того, есть ли для пользователя запись на сервере о дате начала тренировок.
У существующих пользователей, которые уже успели начать тренироваться до релиза этого приложения (в старом приложении или на сайте), проблема не возникала, потому что сервер возвращал валидные данные (не null) - тут стандартные методы декодирования работали корректно, потому что все поля имели ожидаемые типы данных.
Исправление проблемы
Пришлось создать специальные методы декодирования, которые корректно обрабатывают все возможные случаи от сервера, и конечно на этот раз добавил тесты для этой логики, код можно посмотреть в гитхабе, а тут просто перечислю сценарии:
- Должен декодировать стандартный ISO8601 формат без дробных секунд
- Должен декодировать ISO8601 формат с таймзоной +03:00
- Должен декодировать ISO8601 формат с дробными секундами (одна цифра)
- Должен декодировать ISO8601 формат с дробными секундами (две цифры)
- Должен декодировать ISO8601 формат с дробными секундами (три цифры)
- Должен декодировать ISO8601 формат с дробными секундами (четыре цифры)
- Должен выбрасывать ошибку для невалидной даты
- Должен выбрасывать ошибку для пустой строки
- Должен выбрасывать ошибку для неправильного формата даты
- Должен декодировать опциональное поле Date? с валидной датой
- Должен декодировать опциональное поле Date? с null значением
- Должен декодировать опциональное поле Date? с отсутствующим ключом
- Должен декодировать server date time формат без часового пояса
- Должен декодировать опциональное поле Date? с server date time форматом
- Должен декодировать минимальную дату
- Должен декодировать минимальную дату в server date time формате
- Должен декодировать дату с максимальными дробными секундами
- Должен декодировать ISO short date формат
- Должен декодировать опциональное поле Date? с ISO short date форматом
Тесты во втором релизе
Все тесты генерировал с помощью нейросети с использованием нескольких простых правил, о чем напишу в другой раз, а на исправление проблемы вместе с написанием новых тестов потратил около часа.
После этого оформил запрос на ускоренную проверку релиза и ... два дня разбирался с модераторами и отвечал на их вопросы 😁
Заключение
Тестируем не только основную логику приложения, но и декодирование моделей, особенно в пет-проектах ☝️
Бонус
Год назад еще можно было сказать, что автотесты - это дорого, сложно, непонятно, непривычно и так далее. Потому что многие еще не умели пользоваться или не знали про режим агента у популярных IDE типа cursor/windsurf.
Сейчас нельзя сказать, что ты не умеешь писать тесты, или что это дорого и долго - подобные вещи будут просто наглым враньем.
Если не хочется разбираться в новых инструментах - нужно так и говорить прямо, что все, пора на пенсию, потому что слишком сложно сказать нейросети написать тесты для логики или для модели.
Предлагаю всем, кто еще не пробовал генерировать тесты с нейросетью - начните это делать наконец-то, чтобы не ковыряться во внезапных багах и не ломать уже существующий функционал наших классных приложений 😉
Если кто-то переживает, что у нас станет меньше работы при снижении количества багов, то это заблуждение - работы меньше не станет, как и багов, зато стабильности будет больше 👍