כתיבת הרצת בדיקה מפוצלת של IremoteTest

כשכותבים מפעיל בדיקות, חשוב להביא בחשבון את יכולת ההתאמה לעומס. לשאלה בצ'אט "אם הרצת המבחן שלי הייתה צריכה להריץ 200 אלף מקרי בדיקה" כמה זמן זה ייקח?

פיצול הוא אחת מהתשובות הזמינות באיחוד שירותי הסחר. לשם כך צריך לפצל את כל הבדיקות שנדרשות למפעיל לכמה קטעים שאפשר לבצע במקביל.

בדף הזה נסביר איך להפוך את ה-runner לניתן לחלוקה לחלקים ב-Tradefed.

ממשק להטמעה

הממשק החשוב ביותר שצריך להטמיע כדי שייחשב לפיצול TF הוא IShardableTest, שמכיל שתי שיטות: split(int numShard) ו-split().

אם החלוקה תלויה במספר הפיצולים שביקשתם, צריך להטמיע את split(int numShard). אחרת, מטמיעים את split().

כשמפעילים פקודת בדיקה של TF עם פרמטרים של חלוקה לפלחים --shard-count ו---shard-index, ‏TF מבצע חזרה על כל IRemoteTest כדי לחפש אלה שמטמיעים את IShardableTest. אם הוא ימצא, הוא יפעיל את split כדי לקבל אובייקט IRemoteTest חדש להרצת קבוצת משנה של מקרי בדיקה לשבר ספציפי.

מה חשוב לדעת על ההטמעה המפוצלת?

  • הריצה יכולה לפצל תנאים מסוימים בלבד. במקרה הזה, צריך להחזיר null כשלא פיצול.
  • נסו לפצל כמה שאפשר: פצלו את ה-runner ליחידות ביצוע שמתאימות לו. זה תלוי בגורם שמריץ את הבדיקה. עבור דוגמה: בדיקת מארח הם מחולקים ברמת הכיתה, כל כיתה למבחן מחולקת לפיצול נפרד.
  • אם הגיוני, הוסיפו כמה אפשרויות כדי לשלוט קצת בפיצול. מוצרים לדוגמה: AndroidJUnitTest מכיל ajur-max-shard כדי לציין את מספר הפיצולים המקסימלי שהוא יכול לפצל אותן, בלי קשר למספר המבוקש.

הטמעה מפורטת לדוגמה

הנה קטע קוד לדוגמה עם הטמעה של IShardableTest. הקוד המלא זמין בכתובת (https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/main/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java)

/**
 * Runs all instrumentation found on current device.
 */
@OptionClass(alias = "installed-instrumentation")
public class InstalledInstrumentationsTest
        implements IDeviceTest, IResumableTest, IShardableTest {
    ...

    /** {@inheritDoc} */
    @Override
    public Collection<IRemoteTest> split(int shardCountHint) {
        if (shardCountHint > 1) {
            Collection<IRemoteTest> shards = new ArrayList<>(shardCountHint);
            for (int index = 0; index < shardCountHint; index++) {
                shards.add(getTestShard(shardCountHint, index));
            }
            return shards;
        }
        // Nothing to shard
        return null;
    }

    private IRemoteTest getTestShard(int shardCount, int shardIndex) {
        InstalledInstrumentationsTest shard = new InstalledInstrumentationsTest();
        try {
            OptionCopier.copyOptions(this, shard);
        } catch (ConfigurationException e) {
            CLog.e("failed to copy instrumentation options: %s", e.getMessage());
        }
        shard.mShardIndex = shardIndex;
        shard.mTotalShards = shardCount;
        return shard;
    }
    ...
}

בדוגמה הזו פשוט נוצרת מכונה חדשה של עצמה ומגדירים לה פרמטרים של פלחים. עם זאת, הלוגיקה של הפיצול יכולה להיות שונה לגמרי בין בדיקה לבדיקה, כל עוד היא גורמת ליצירת קבוצות משנה מקיפות באופן קולקטיבי.

עצמאות

פיצולים צריכים להיות בלתי תלויים! לשני שברי המידע שנוצרו על ידי ההטמעה של split ב-runner לא צריכות להיות יחסי תלות זה בזה, והם לא צריכים לשתף משאבים.

פיצול פיצולים צריך להיות דטרמיניסטי! זה חובה גם כי באותו מצב, שיטת split תמיד צריכה להחזיר את אותה רשימה של פלחים באותו סדר.

הערה: מאחר שכל שריד יכול לפעול במכונות TF שונות, חשוב לוודא שהלוגיקה של split מניבה קבוצות משנה שהן בלתי ניתנות להפרדה ומקיפות באופן דטרמיניסטי.

חלוקה של בדיקה למקטעים באופן מקומי

כדי לפצל בדיקה ב-TF מקומי, אפשר פשוט להוסיף את האפשרות --shard-count כדי בשורת הפקודה.

tf >run host --class com.android.tradefed.UnitTests --shard-count 3

לאחר מכן, TF ייצור פקודות באופן אוטומטי לכל שריד ויפעיל אותן.

tf >l i
Command Id  Exec Time  Device          State
3           0m:03      [null-device-2]  running stub on build 0 (shard 1 of 3)
3           0m:03      [null-device-1]  running stub on build 0 (shard 0 of 3)
3           0m:03      [null-device-3]  running stub on build 0 (shard 2 of 3)

צבירת תוצאות בדיקה

מכיוון ש-TF לא מבצע צבירת תוצאות בדיקה עבור הפעלות מפוצלות, צריך לוודא ששירות הדיווח שלכם תומך בכך.