В этой статье мы рассказывали о реализации новой функции в приложении "Бюджет 2.0", о которой давно просили пользователи. А здесь мы рассмотрим техническую сторону вопроса, которая больше заинтересует разработчиков и тех, кто имеет отношение к продуктовой разработке в IT.
На первый взгляд задача не представляет собой ничего сложного: требуется создавать операции снятия денег с копилок, дублирующие обычные операции расходов, и в случае, если таких операций несколько, создавать вместо нескольких операций снятия с копилок одну общую.
Казалось бы, ну и чего здесь страшного? Пиши алгоритм и публикуй обновление! Но приложение считает деньги пользователей, и если накосячить в алгоритме и удалить десяток-другой операций с реальными кошельками, можно поиметь тонны негатива в отзывах и потерять драгоценные лояльность и пользовательскую оценку.
Unit-тесты как способ обезопасить свой код
Писать тесты для алгоритмов, работающих с базой данных в Android - одно удовольствие. Особенно, если для работы с базой вы используете Room, и у вас уже есть собственная реализация RoomDatabase. В этом случае для тестов вы просто создаёте экземпляр базы данных, которая хранится в оперативной памяти, и делаете с ней всё, что угодно.
- Инициализация экземпляра RoomDatabase, хранящейся в оперативной памяти и существующей только во время проведения тестов.
- Настройка имитации (mock) объекта SharedPreferences, который используется тестируемым алгоритмом, чтобы определить, включена ли у пользователя функция автоматического создания операций с копилками или же нет. В нашем случае всегда возвращает true.
- Вспомогательная функция для очистки содержимого базы данных между тестами.
Как только подготовительные работы для написания тестов завершены, можно приступать к самой интересной части - собственно написанию тестов! Не ограничивайте себя и дайте волю своей фантазии. Главная задача, которую вы можете решить с помощью Unit-тестов - это описать все так называемые крайние случаи и быть уверенным в работе своего алгоритма каждый раз при проведении рефакторинга или расширении существующего функционала.
Вот несколько примеров крайних случаев, в которых нам было необходимо гарантировать корректную работу алгоритма:
- не пересоздавать заново уже существующую операцию снятия с копилки на требуемую сумму (потому что это привело бы к бесконечной пересылке данных между синхронизируемыми устройствами, если пользователь ведёт бюджет совместно с супругой/супругом)
- удалять существующие операции снятия с копилок, если связанные с ними реальные операции расходов уже удалены
- не снимать с копилки больше денег, чем в ней есть и т.д.
Вот пример одного из тестов, проверяющего работу алгоритма в случае, когда операция нужной категории произведена, а копилка, с которой можно снять деньги, ещё не создана:
- Очистка тестовой базы данных и её наполнение исходными данными.
- Вызов функции, содержащей тестируемый алгоритм.
- Сверка фактических результатов работы алгоритма и наших ожиданий.
Итогом нашей работы над этим конкретным обновлением стал алгоритм, занимающий менее 100 строк кода, а также Unit-тесты практически на 1000 строк. Зато теперь при публикации обновления мы уверены, что не накосячили по крайней мере в самых очевидных вещах.
Кажется, что существует прямая зависимость между количеством написанных для кода тестов и уровнем душевного спокойствия разработчика. Кроме того, тренируясь писать Unit-тесты, вы развиваете в себе важнейшее для программиста умение - определять в собственных алгоритмах потенциальные дыры и ликвидировать их до тех пор, пока это не сделают тестировщики, менеджеры или, не дай бог, ваши пользователи.