Управляйте своим приложением из Macrobenchmark (Views).

Концепции и реализация Jetpack Compose

В отличие от большинства тестов пользовательского интерфейса Android, тесты Macrobenchmark выполняются в отдельном процессе от самого приложения. Это необходимо для обеспечения возможности, например, остановки процесса приложения и компиляции байт-кода DEX в машинный код.

Вы можете управлять состоянием своего приложения с помощью библиотеки UIAutomator или других механизмов, позволяющих контролировать целевое приложение из тестового процесса. Вы не можете использовать Espresso или ActivityScenario для Macrobenchmark, поскольку они ожидают запуска в общем процессе с приложением.

В следующем примере находится RecyclerView по его идентификатору ресурса, и он несколько раз прокручивается вниз:

Котлин

@Test
fun scrollList() {
    benchmarkRule.measureRepeated(
        // ...
        setupBlock = {
            uiAutomator {
                // Before starting to measure, navigate to the UI to be measured
                startIntent(Intent("$packageName.RECYCLER_VIEW_ACTIVITY"))
            }
        }
    ) {
        uiAutomator {
            val recycler = onElement { className == "androidx.recyclerview.widget.RecyclerView" }
            // Scroll down several times
            repeat(3) { recycler.fling(Direction.DOWN) }
        }

    }
}

Java

@Test
public void scrollList() {
    benchmarkRule.measureRepeated(
        // ...
        /* setupBlock */ scope -> {
            // Before measuring, navigate to the UI to be measured.
            val intent = Intent("$packageName.RECYCLER_VIEW_ACTIVITY")
            scope.startActivityAndWait();
            return Unit.INSTANCE;
        },
        /* measureBlock */ scope -> {
            UiDevice device = scope.getDevice();
            UiObject2 recycler = device.findObject(By.res(scope.getPackageName(), "recycler"));

            // Set gesture margin to avoid triggering gesture navigation
            // with input events from automation.
            recycler.setGestureMargin(device.getDisplayWidth() / 5);

            // Fling the recycler several times.
            for (int i = 0; i < 3; i++) {
                recycler.fling(Direction.DOWN);
            }

            return Unit.INSTANCE;
        }
    );
}

Вашему бенчмарку не обязательно прокручивать пользовательский интерфейс. Вместо этого он может, например, запускать анимацию. Также ему не обязательно использовать именно UI Automator. Он собирает метрики производительности до тех пор, пока происходит отрисовка кадров.

Иногда возникает необходимость протестировать производительность тех частей приложения, которые недоступны извне. Например, это может быть доступ к внутренним Activity, помеченным параметром exported=false , переход к Fragment или удаление части пользовательского интерфейса с помощью свайпа. Для тестирования производительности необходимо вручную переходить к этим частям приложения, как это делает пользователь.

Для ручной навигации измените код внутри setupBlock{} , добавив нужный эффект, например, нажатие кнопки или свайп. Ваш measureBlock{} содержит только те манипуляции с пользовательским интерфейсом, которые вы хотите протестировать:

Котлин

@Test
fun nonExportedActivityScrollList() {
    benchmarkRule.measureRepeated(
        // ...
        setupBlock = setupBenchmark()
    ) {
        // ...
    }
}

private fun setupBenchmark(): MacrobenchmarkScope.() -> Unit = {
    uiAutomator {
        // Before starting to measure, navigate to the UI to be measured
        startApp(TARGET_PACKAGE)
        // click a button to launch the target activity.
        onElement { textAsString() == "RecyclerView" }.click()
        // wait until the activity is shown
        waitForStableInActiveWindow()
    }
}

Java

@Test
public void scrollList() {
    benchmarkRule.measureRepeated(
        // ...
        /* setupBlock */ scope -> {
            // Before measuring, navigate to the default activity.
            scope.startActivityAndWait();

            // Click a button to launch the target activity.
            // While you use resourceId here to find the button, you can also
            // use accessibility info or button text content.
            UiObject2 launchRecyclerActivity = scope.getDevice().findObject(
                By.res(packageName, "launchRecyclerActivity")
            )
            launchRecyclerActivity.click();

            // Wait until activity is shown.
            scope.getDevice().wait(
                Until.hasObject(By.clazz("$packageName.NonExportedRecyclerActivity")),
                10000L
            )

            return Unit.INSTANCE;
        },
        /* measureBlock */ scope -> {
            // ...
        }
    );
}
{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}