Автоматическое тестирование приложений
Dejavu: тестирование рекомпозиций для Jetpack Compose
Dejavu преобразует поведение рекомпозиции в тестовые утверждения.
В Square большая часть приложения построена на основе Workflow. Существует внутренняя система тестирования, которая позволяет точно определить, сколько проходов рендеринга запускается для конкретного взаимодействия, и это затем контролируется системой непрерывной интеграции (CI).
Я возглавлял команду, которая оптимизировала экран выбора способа оплаты в приложении Square Point of Sale. Мы проанализировали дерево рендеринга, удалили ненужные проходы и сократили общую задержку на 30%! Каждый рендеринг выполняет дополнительную работу, поэтому объединение усилий и снижение количества рендерингов напрямую способствовали повышению производительности.
Больше всего успеха мы добились после этого. Мы написали тесты, которые подтверждали достигнутое нами новое и улучшенное количество рендерингов. Если бы в будущем изменение привело к появлению дополнительных рендерингов, CI бы выдала ошибку, и наши достижения в производительности не смогли бы незаметно снизиться.
Проблема с Compose
В Workflow есть рендеринг. В Compose есть рекомпозиция.
Существует лишь несколько способов получить представление о производительности Composable.
- Layout Inspector требует работы в IDE, ручной проверки запущенного приложения и не работает в CI.
- «SideEffect гарантирует, что эффект выполняется после каждой успешной рекомпозиции» и может использоваться для их подсчета. Но они засоряют производственный код отладочной инфраструктурой.
Ни один из них не предоставляет тестируемого контракта, который можно было бы обеспечить при каждом запросе на слияние.
Представляем Dejavu
Dejavu преобразует поведение рекомпозиции в тестовые утверждения. Никаких изменений в производственном коде, кроме Modifier.testTag() (который вы, вероятно, уже используете).
Настройка
// Create a Recomposition Tracking Rule
@get:Rule
val composeTestRule = createRecompositionTrackingRule()
@Test
fun incrementCounter_onlyValueRecomposes() {
// Perform an action
composeTestRule.onNodeWithTag("inc_button")
.performClick()
// Assert that Composables change like you expect
composeTestRule.onNodeWithTag("counter_value")
.assertRecompositions(exactly = 1)
// Or assert that they remain stable
composeTestRule.onNodeWithTag("counter_title")
.assertStable() // asserts recompositions = 0
}
Та же схема, что и в любом тесте Compose UI.
- Найти узел по тегу
- Выполнить действие
- Проверить ожидаемое количество рекомпозиций
При сбое теста вы получите диагностические сообщения с объяснением причины:
dejavu.UnexpectedRecompositionsError: Recomposition assertion failed for testTag='product_header'
Composable: demo.app.ui.ProductHeader (ProductList.kt:29)
Expected: exactly 0 recomposition(s)
Actual: 1 recomposition(s)
All tracked composables:
ProductListScreen = 1
ProductHeader = 1 <-- FAILED
ProductItem = 1
Recomposition timeline:
#1 at +0ms — param slots changed: [1] | parent: ProductListScreen
Possible cause:
1 state change(s) of type Int
Parameter/parent change detected (dirty bits set)
См. руководство по сообщениям об ошибках для получения дополнительной информации.
Что отличает Dejavu
Dejavu интегрируется с CompositionTracer, поэтому нет необходимости в плагине компилятора, манипуляциях с байт-кодом или плагине Gradle.
Анализ причинности — это попытка по мере возможностей объяснить, почему произошла рекомпозиция. Он отслеживает изменения состояния Snapshot и обратно сопоставляет «грязные» биты (dirty bits) со слотами параметров (parameter slots).
Все Composable отслеживаются по умолчанию. testTag требуется только для API утверждений с onNodeWithTag("x").
Сам трассировщик отслеживает каждый композабл, который отслеживается через CompositionTracer, независимо от того, есть ли у него testTag. Сопоставление тегов просто связывает testTag и имя функции, чтобы утверждения могли найти правильный счетчик.
Слепое пятно ИИ-агента
ИИ-агенты пишут код Compose. Они рефакторят экраны, поднимают состояние, извлекают компоненты, и в основном у них это хорошо получается. В настоящее время у агента нет возможности точно определить, повлияло ли изменение негативно (или позитивно) на рекомпозиции.
Мы, люди, полагаемся на модель, чтобы она всё сделала правильно, пока на нас обрушивается всё растущая лавина проверок PR. UI-тесты могут проходить успешно и выглядеть нормально в записях CI, но пользователи начинают оставлять негативные отзывы из-за подтормаживаний интерфейса.
Dejavu предоставляет вам, вашей системе CI и вашим агентам возможность запускать тесты пользовательского интерфейса и проверять отсутствие неожиданных изменений.
Сообщения об ошибках работают для обеих аудиторий. Человек или агент могут прочитать сообщение об ошибке и понять, что нужно исправить. И если агент вызывает регрессию при рекомпозиции, CI завершается с ошибкой, и запрос на слияние не срабатывает.
Вы также можете передавать события в реальном времени с помощью Dejavu.enable(app, logToLogcat = true). Агент, запускающий adb logcat -s Dejavu, получает прямую трансляцию состояния композиции для каждого экземпляра во время итерации по запущенному приложению.
Как начать
androidTestImplementation("me.mmckenna.dejavu:dejavu:0.1.2")
Есть документация, руководство по запуску, примеры, API и GitHub.
Если вы когда-либо задавались вопросом, почему композабл перерисовывается, глядя на Layout Inspector, или хотели бы просто проверить это в тесте, попробуйте это и, пожалуйста, дайте мне знать, как всё прошло!
Что дальше?
Много реального тестирования! Я добавил довольно обширный набор тестов для проверки корректности, но Compose настолько выразителен и адаптируем, что я уверен, что что-то упустил. Если вы обнаружите ошибки, я буду рад узнать, в чём они заключаются.
-
Видео и подкасты для разработчиков4 недели назад
КодРевью лидера мнений: как можно нарушить сразу все принципы разработки
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2026.8
-
Разработка4 недели назад
Никакого программирования до 10 утра
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2026.9
