בדיקת קוד בתוך דגלים להשקת תכונות

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

  • הבדיקות צריכות לכלול את ההתנהגויות של התכונה כשהיא מופעלת וגם כשהיא מושבתת.
  • במהלך הבדיקה, חובה להשתמש במנגנונים הרשמיים כדי להגדיר ערכי דגל.
  • בבדיקות xTS לא אמורה להיות אפשרות לשנות את ערכי הדגלים.

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

בדיקת הקוד שסומן

תרחיש בדיקה המנגנון שבו נעשה שימוש
בדיקות מקומיות כשערכי הדגלים משתנים לעיתים קרובות ‫Android debug bridge (גשר לניפוי באגים ב-Android) כמו שמתואר במאמר בנושא שינוי הערך של דגל בזמן ריצה
בדיקה מקומית כשערכי הדגלים לא משתנים לעיתים קרובות קובץ ערכי הסימונים, כפי שמוסבר במאמר הגדרת ערכי סימונים להשקת תכונות
בדיקת קצה לקצה שבה ערכי הדגלים משתנים FeatureFlagTargetPreparer כמו שמוסבר במאמר יצירת בדיקות מקצה לקצה
בדיקות יחידה שבהן ערכי הדגלים משתנים SetFlagsRule עם @EnableFlags ו-@DisableFlags כמו שמתואר במאמר יצירת בדיקות יחידה (Java ו-Kotlin) או במאמר יצירת בדיקות יחידה (C ו-C++)
בדיקות מקצה לקצה או בדיקות יחידה שבהן אי אפשר לשנות את ערכי הדגלים CheckFlagsRule כמו שמוסבר במאמר יצירת בדיקות מקצה לקצה או בדיקות יחידה שבהן ערכי הדגלים לא משתנים

יצירת בדיקות מקצה לקצה

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

אפשר להחיל את הפונקציונליות של המחלקה FeatureFlagTargetPreparer ברמת מודול הבדיקה וברמת הגדרת הבדיקה.

שימוש ב-FeatureFlagTargetPreparer בהגדרות של מודול בדיקה

כדי להחיל את FeatureFlagTargetPreparer בתצורת מודול בדיקה, צריך לכלול את FeatureFlagTargetPreparer ואת הדגל שערכי ברירת המחדל שלו מוחלפים בקובץ התצורה של מודול הבדיקה AndroidTest.xml:

  <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer">
        <option name="flag-value"
            value="permissions/com.android.permission.flags.device_aware_permission_grant=true"/>
        <option name="flag-value"
            value="virtual_devices/android.companion.virtual.flags.stream_permissions=true"/>
    </target_preparer>

איפה:

  • הערך של target.preparer class הוא תמיד com.android.tradefed.targetprep.FeatureFlagTargetPreparer.
  • option הוא שינוי ברירת המחדל של הדגל, כאשר name מוגדר תמיד לערך flag-value ו-value מוגדר לערך namespace/aconfigPackage.flagName=true|false.

יצירת מודולים של בדיקות עם פרמטרים על סמך מצבי הדגל

כדי ליצור מודולים של בדיקות עם פרמטרים על סמך מצבי דגל:

  1. כוללים את FeatureFlagTargetPreparer בקובץ התצורה של מודול הבדיקה AndroidTest.xml:

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. מציינים את האפשרויות של ערך הדגל בקטע test_module_config של קובץ build‏ Android.bp:

    android_test {
        name: "MyTest"
        ...
    }
    
    test_module_config {
        name: "MyTestWithMyFlagEnabled",
        base: "MyTest",
        ...
        options: [
            {name: "flag-value", value: "telephony/com.android.internal.telephony.flags.oem_enabled_satellite_flag=true"},
        ],
    }
    
    test_module_config {
        name: "MyTestWithMyFlagDisabled",
        base: "MyTest",
        ...
        options: [
            {name: "flag-value", value: "telephony/com.android.internal.telephony.flags.carrier_enabled_satellite_flag=true"},
        ],
    }
    

    השדה options מכיל את הדגלים שמוגדרים כברירת מחדל, כאשר name תמיד מוגדר לערך flag-value ו-value מוגדר לערך namespace/aconfigPackage.flagName=true|false.

יצירת בדיקות יחידה (Java ו-Kotlin)

בקטע הזה מתואר איך לבטל את הערכים של דגל aconfig ברמת המחלקה והשיטה (לכל בדיקה) בבדיקות Java ו-Kotlin.

כדי לכתוב בדיקות יחידה אוטומטיות בבסיס קוד גדול עם מספר רב של דגלים, פועלים לפי השלבים הבאים:

  1. כדי לבדוק את כל ענפי הקוד, משתמשים במחלקה SetFlagsRule עם ההערות @EnableFlags ו-@DisableFlags.
  2. כדי להימנע מבאגים נפוצים בבדיקות, כדאי להשתמש בשיטה SetFlagsRule.ClassRule.
  3. אפשר להשתמש ב-FlagsParameterization כדי לבדוק את הכיתות במגוון רחב של תצורות של דגלים.

בדיקה של כל ענפי הקוד

בפרויקטים שמשתמשים במחלקה הסטטית כדי לגשת לדגלים, מסופקת המחלקה SetFlagsRule helper כדי לבטל את ערכי הדגלים. בקטע הקוד הבא אפשר לראות איך לכלול את SetFlagsRule ולהפעיל כמה דגלים בבת אחת:

  import android.platform.test.annotations.EnableFlags;
  import android.platform.test.flag.junit.SetFlagsRule;
  import com.example.android.aconfig.demo.flags.Flags;
  ...
    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Test
    @EnableFlags({Flags.FLAG_FLAG_FOO, Flags.FLAG_FLAG_BAR})
    public void test_flag_foo_and_flag_bar_turned_on() {
    ...
    }

איפה:

  • @Rule היא הערה שמשמשת להוספת התלות בדגל JUnit של המחלקה SetFlagsRule.
  • SetFlagsRule היא מחלקה מסייעת שנועדה לשנות את ערכי הסימון. מידע על האופן שבו SetFlagsRule קובע ערכי ברירת מחדל זמין במאמר ערכי ברירת מחדל של מכשירים.
  • @EnableFlags היא הערה שמקבלת מספר שרירותי של שמות של דגלים. כשמשביתים דגלים, משתמשים בדגל @DisableFlags. אפשר להחיל את ההערות האלה על שיטה או על מחלקה.

מגדירים ערכי דגל לכל תהליך הבדיקה, החל מ-SetFlagsRule, שהוא לפני כל שיטות ההגדרה עם ההערה @Before בבדיקה. ערכי הדגלים חוזרים למצב הקודם שלהם כשהפונקציה SetFlagsRule מסתיימת, כלומר אחרי כל שיטות ההגדרה עם ההערה @After.

מוודאים שהדגלים מוגדרים בצורה נכונה

כמו שצוין קודם, נעשה שימוש ב-SetFlagsRule עם ההערה @Rule של JUnit, מה שאומר שלא ניתן להשתמש ב-SetFlagsRule כדי לוודא שהדגלים מוגדרים בצורה נכונה במהלך הבדיקה של בנאי המחלקה, או בכל שיטה עם ההערות @BeforeClass או @AfterClass.

כדי לוודא שפריטי הבדיקה נוצרים עם ערך המחלקה הנכון, משתמשים בשיטה SetFlagsRule.ClassRule כדי שפריטי הבדיקה לא ייווצרו עד שמוגדרת שיטת הגדרה עם ההערה @Before:

  import android.platform.test.annotations.EnableFlags;
  import android.platform.test.flag.junit.SetFlagsRule;
  import com.example.android.aconfig.demo.flags.Flags;

  class ExampleTest {
    @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
    @Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();

    private DemoClass underTest = new DemoClass();

    @Test
    @EnableFlags(Flags.FLAG_FLAG_FOO)
    public void test_flag_foo_turned_on() {
      ...
    }
  }

אם מוסיפים את SetFlagsRule.ClassRule class rule, test_flag_foo_turned_on הבדיקה נכשלת לפני ההפעלה כש-FLAG_FLAG_FOO נקרא על ידי ה-constructor של DemoClass.

אם צריך להפעיל דגל לכל הכיתה, מעבירים את ההערה @EnableFlags לרמת הכיתה (לפני הצהרת הכיתה). העברת ההערה לרמת המחלקה מאפשרת ל-SetFlagsRule.ClassRule לוודא שהדגל מוגדר בצורה נכונה במהלך הבנאי של מחלקת הבדיקה, או במהלך כל שיטה עם ההערות @BeforeClass או @AfterClass.

הרצת בדיקות בכמה הגדרות של דגלים

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

...
import com.example.android.aconfig.demo.flags.Flags;
...

@RunWith(ParameterizedAndroidJunit4::class)
class FooBarTest {
    @Parameters(name = "{0}")
    public static List<FlagsParameterization> getParams() {
        return FlagsParameterization.allCombinationsOf(Flags.FLAG_FOO, Flags.FLAG_BAR);
    }

    @Rule
    public SetFlagsRule mSetFlagsRule;

    public FooBarTest(FlagsParameterization flags) {
        mSetFlagsRule = new SetFlagsRule(flags);
    }

    @Test public void fooLogic() {...}

    @DisableFlags(Flags.FLAG_BAR)
    @Test public void legacyBarLogic() {...}

    @EnableFlags(Flags.FLAG_BAR)
    @Test public void newBarLogic() {...}
}

שימו לב: ב-SetFlagsRule, אבל בלי פרמטריזציה, המחלקה הזו מריצה שלושה בדיקות (fooLogic, legacyBarLogic ו-newBarLogic). השיטה fooLogic מורצת עם הערכים של FLAG_FOO ו-FLAG_BAR שמוגדרים במכשיר.

כשמוסיפים פרמטרים, הפונקציה FlagsParameterization.allCombinationsOf יוצרת את כל השילובים האפשריים של הדגלים FLAG_FOO ו-FLAG_BAR:

  • FLAG_FOO הוא true ו-FLAG_BAR הוא true
  • FLAG_FOO הוא true ו-FLAG_BAR הוא false
  • FLAG_FOO הוא false ו-FLAG_BAR הוא true
  • הערך של FLAG_FOO הוא false והערך של FLAG_BAR הוא false

במקום לשנות ישירות את ערכי הדגלים, ההערות @DisableFlags ו-@EnableFlags משנות את ערכי הדגלים על סמך תנאי הפרמטרים. לדוגמה, legacyBarLogic פועל רק כש-FLAG_BAR מושבת, וזה קורה בשניים מתוך ארבעת השילובים של הדגלים. השילוב legacyBarLogic לא נכלל בשני השילובים האחרים.

יש שתי שיטות ליצירת פרמטרים לדגלים:

  • FlagsParameterization.allCombinationsOf(String...) מבצע 2 בחזקת n הרצות של כל בדיקה. לדוגמה, דגל אחד מריץ 2x בדיקות, או ארבעה דגלים מריצים 16x בדיקות.

  • FlagsParameterization.progressionOf(String...) מבצע n+1 הרצות של כל בדיקה. לדוגמה, דגל אחד מפעיל 2x בדיקות וארבעה דגלים מפעילים 5x דגלים.

יצירת בדיקות יחידה (C ו-C++)

‫AOSP כולל פקודות מאקרו של ערכי דגלים לבדיקות C ו-C++‎ שנכתבו במסגרת GoogleTest.

  1. במקור הבדיקה, כוללים את הגדרות המאקרו וספריות שנוצרו על ידי aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. במקור הבדיקה, במקום להשתמש במאקרו TEST ובמאקרו TESTF לתרחישי הבדיקה, משתמשים במאקרו TEST_WITH_FLAGS ובמאקרו TEST_F_WITH_FLAGS:

    #define TEST_NS android::cts::flags::tests
    
    ...
    
    TEST_F_WITH_FLAGS(
      TestFWithFlagsTest,
      requies_disabled_flag_enabled_skip,
      REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(TEST_NS, readwrite_enabled_flag))
    ) {
      TestFail();
    }
    
    ...
    
    TEST_F_WITH_FLAGS(
      TestFWithFlagsTest,
      multi_flags_for_same_state_skip,
      REQUIRES_FLAGS_ENABLED(
          ACONFIG_FLAG(TEST_NS, readwrite_enabled_flag),
          LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag)
      )
    ) {
      TestFail();
    }
    
    ...
    
    TEST_WITH_FLAGS(
      TestWithFlagsTest,
      requies_disabled_flag_enabled_skip,
      REQUIRES_FLAGS_DISABLED(
          LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_enabled_flag))
    ) {
      FAIL();
    }
    
    ...
    
    TEST_WITH_FLAGS(
      TestWithFlagsTest,
      requies_enabled_flag_enabled_executed,
      REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_NS, readwrite_enabled_flag))
    ) {
      TestWithFlagsTestHelper::executed_tests.insert(
          "requies_enabled_flag_enabled_executed");
    }
    

    איפה:

    • פקודות המאקרו TEST_WITH_FLAGS ו-TEST_F_WITH_FLAGS משמשות במקום פקודות המאקרו TEST ו-TEST_F.
    • REQUIRES_FLAGS_ENABLED מגדיר קבוצה של דגלים להפצת תכונות שצריכים לעמוד בתנאי ההפעלה. אפשר לכתוב את הסימונים האלה בפקודות מאקרו של ACONFIG_FLAG או של LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED מגדיר קבוצה של דגלי תכונות שצריכים לעמוד בתנאי ההשבתה. אפשר לכתוב את הסימונים האלה בפקודות מאקרו של ACONFIG_FLAG או של LEGACY_FLAG.
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) הוא מאקרו שמשמש לסימונים שמוגדרים בקובצי תצורה. המאקרו הזה מקבל מרחב שמות (TEST_NS) ושם של דגל (readwrite_enabled_flag).
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) הוא מאקרו שמשמש כברירת מחדל להגדרת דגלים בהגדרות המכשיר.
  3. בקובץ ה-build‏ Android.bp, מוסיפים את הספריות שנוצרו על ידי aconfig ואת ספריות המאקרו הרלוונטיות כתלות בבדיקה:

    cc_test {
      name: "FlagMacrosTests",
      srcs: ["src/FlagMacrosTests.cpp"],
      static_libs: [
          "libgtest",
          "libflagtest",
          "my_aconfig_lib",
      ],
      shared_libs: [
          "libbase",
          "server_configurable_flags",
      ],
      test_suites: ["general-tests"],
      ...
    }
    
  4. מריצים את הבדיקות באופן מקומי באמצעות הפקודה הבאה:

    atest FlagMacrosTests
    

    אם התכונה הניסיונית my_namespace.android.myflag.tests.my_flag מושבתת, תוצאת הבדיקה היא:

    [1/2] MyTest#test1: IGNORED (0ms)
    [2/2] MyTestF#test2: PASSED (0ms)
    

    אם הדגל my_namespace.android.myflag.tests.my_flag מופעל, תוצאת הבדיקה היא:

    [1/2] MyTest#test1: PASSED (0ms)
    [2/2] MyTestF#test2: IGNORED (0ms)
    

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

במקרים שבהם אי אפשר לבטל את ההגדרה של הדגלים ואפשר לסנן את הבדיקות רק אם הן מבוססות על מצב הדגל הנוכחי, צריך להשתמש בכלל CheckFlagsRule עם ההערות RequiresFlagsEnabled ו-RequiresFlagsDisabled.

בשלבים הבאים מוסבר איך ליצור ולהריץ בדיקת יחידה או בדיקה מקצה לקצה שבה אי אפשר לבטל את הערכים של הדגלים:

  1. בקוד הבדיקה, משתמשים ב-CheckFlagsRule כדי להחיל סינון של הבדיקה. בנוסף, משתמשים בהערות Java‏ RequiresFlagsEnabled ו-RequiredFlagsDisabled כדי לציין את דרישות הדגל לבדיקה.

    הבדיקה בצד המכשיר משתמשת במחלקה DeviceFlagsValueProvider:

    @RunWith(JUnit4.class)
    public final class FlagAnnotationTest {
      @Rule
      public final CheckFlagsRule mCheckFlagsRule =
              DeviceFlagsValueProvider.createCheckFlagsRule();
    
      @Test
      @RequiresFlagsEnabled(Flags.FLAG_FLAG_NAME_1)
      public void test1() {}
    
      @Test
      @RequiresFlagsDisabled(Flags.FLAG_FLAG_NAME_1)
      public void test2() {}
    }
    

    הבדיקה בצד המארח משתמשת במחלקה HostFlagsValueProvider:

    @RunWith(DeviceJUnit4ClassRunner.class)
    public final class FlagAnnotationTest extends BaseHostJUnit4Test {
      @Rule
      public final CheckFlagsRule mCheckFlagsRule =
              HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
    
      @Test
      @RequiresFlagsEnabled(Flags.FLAG_FLAG_NAME_1)
      public void test1() {}
    
      @Test
      @RequiresFlagsDisabled(Flags.FLAG_FLAG_NAME_1)
      public void test2() {}
    }
    
  2. מוסיפים את jflag-unit ואת הספריות שנוצרו על ידי aconfig לקטע static_libs בקובץ ה-build של הבדיקה:

    android_test {
        name: "FlagAnnotationTests",
        srcs: ["*.java"],
        static_libs: [
            "androidx.test.rules",
            "my_aconfig_lib",
            "flag-junit",
            "platform-test-annotations",
        ],
        test_suites: ["general-tests"],
    }
    
  3. מריצים את הבדיקה באופן מקומי באמצעות הפקודה הבאה:

    atest FlagAnnotationTests
    

    אם הדגל Flags.FLAG_FLAG_NAME_1 מושבת, תוצאת הבדיקה היא:

    [1/2] com.cts.flags.FlagAnnotationTest#test1: ASSUMPTION_FAILED (10ms)
    [2/2] com.cts.flags.FlagAnnotationTest#test2: PASSED (2ms)
    

    אחרת, תוצאת הבדיקה היא:

    [1/2] com.cts.flags.FlagAnnotationTest#test1: PASSED (2ms)
    [2/2] com.cts.flags.FlagAnnotationTest#test2: ASSUMPTION_FAILED (10ms)
    

ערכי ברירת מחדל של המכשיר

הערך המאותחל SetFlagsRule משתמש בערכי דגלים מהמכשיר. אם לא משנים את ערך הדגל במכשיר, למשל באמצעות adb, ערך ברירת המחדל זהה להגדרת הגרסה של ה-build. אם הערך במכשיר שונה מברירת המחדל, SetFlagsRule משתמש בערך ששונה כברירת המחדל.

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

אחרי כל בדיקה, SetFlagsRule משחזר את מופע FeatureFlags ב-Flags למצב FeatureFlagsImpl המקורי שלו, כדי שלא יהיו לו תופעות לוואי על שיטות וסוגים אחרים של בדיקות.