Yesterday

135. Тестируем парсинг моделей

Недавно я выпустил в аппстор приложение для тренировок, предварительно провел много ручных тестов и написал больше тысячи автотестов. В этой статье расскажу про важность забытых мной тестов 😅

Тесты в первом релизе

Автотесты на момент релиза первой версии приложения

Вот обобщенный перечень тестов, которые были на момент первого релиза:

  • Тесты для экрана выполнения тренировки (настройка, завершение шагов, получение результата, работа с таймером, турбо-дни и т.д.)
  • Тесты для экрана превью тренировки (построение активности дня, редактирование комментариев, обновление данных, сохранение тренировки и т.д.)
  • Тесты для управления статусом программы (получение статуса, запуск, сброс, синхронизация, журнал синхронизации, интеграционные тесты и т.д.)
  • Тесты для работы с дневником тренировок (базовые операции, комментарии, разрешение конфликтов, обработка ошибок, тренировки и т.д.)
  • Тесты для работы с инфопостами (синхронизация, парсинг, управление ресурсами, работа с YouTube, избранное и т.д.)
  • Тесты для работы с прогрессом пользователя (базовые операции, синхронизация, работа с фотографиями, калькулятор прогресса и т.д.)
  • Тесты для работы с пользовательскими упражнениями
  • Тесты для настроек приложения
  • Тесты для воспроизведения звука
  • Тесты для обновления стран
  • Тесты для обработки изображений
  • Тесты для управления ресурсами изображений
  • Тесты для работы с YouTube видео
  • Тесты для моделей тренировок
  • Тесты для моделей дней
  • Тесты для моделей прогресса
  • Тесты для моделей пользователя
  • Тесты для моделей синхронизации
  • Тесты для других моделей (HomeScreenModel, ShareAppURL, TimerSound)
  • Тесты для создания программы тренировок (инициализация дня, работа с активностью дня, турбо-дни, пользовательские упражнения, типы выполнения и т.д.)

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

Проблема первого релиза

Про релиз сделали пост на сайте, с которым синхронизируется приложение, и в их телеграм-чате - там я случайно увидел пост одного из пользователей:

Всем привет, была у кого такая проблема в приложении на IOS?

Моя реакция, когда я увидел этот пост: 🤯🤦‍♂️

Для разработчика проблема очевидна: не получилось спарсить (обработать) какой-то из ответов сервера, а судя по скриншоту, это вообще происходит при первой авторизации, т.к. на главном экране ничего нет кроме алерта.

Это был единственный момент, который я не протестировал: зарегистрировать нового пользователя на сайте, а потом авторизоваться в 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.

Сейчас нельзя сказать, что ты не умеешь писать тесты, или что это дорого и долго - подобные вещи будут просто наглым враньем.

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

Предлагаю всем, кто еще не пробовал генерировать тесты с нейросетью - начните это делать наконец-то, чтобы не ковыряться во внезапных багах и не ломать уже существующий функционал наших классных приложений 😉

Если кто-то переживает, что у нас станет меньше работы при снижении количества багов, то это заблуждение - работы меньше не станет, как и багов, зато стабильности будет больше 👍