Как управлять приложением с помощью Macrobenchmark (представления)

Основные понятия и реализация Jetpack Compose

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

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

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

Kotlin

@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. Показатели эффективности собираются, пока создаются кадры.

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

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

Kotlin

@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 -> {
            // ...
        }
    );
}