שליטה באפליקציה מ-Macrobenchmark (תצוגות)

מושגים והטמעה ב-Jetpack Compose

בניגוד לרוב בדיקות ממשק המשתמש ב-Android, בדיקות Macrobenchmark פועלות בתהליך נפרד מהאפליקציה עצמה. הפעולה הזו נדרשת כדי לאפשר דברים כמו עצירה של תהליך האפליקציה והידור מקוד בייט של DEX לשפת מכונה.

אפשר להגדיר את מצב האפליקציה באמצעות ספריית UIAutomator או מנגנונים אחרים שיכולים לשלוט באפליקציית היעד מתוך תהליך הבדיקה. אי אפשר להשתמש ב-Espresso או ב-ActivityScenario ל-Macrobenchmark כי הן אמורות לפעול בתהליך משותף עם האפליקציה.

בדוגמה הבאה מוצג חיפוש של 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 -> {
            // ...
        }
    );
}