יצירת בדיקת מערכת של SDV

בדיקות מערכת הן כל בדיקות ה-SDV שנוצרו באמצעות מסגרת בדיקות ה-SDV.

יצירת בדיקה

מיקום הבדיקה

  • <test_repository_root>/sample_tests
  • <test_repository_root>/e2e_tests
  • <test_repository_root>/long_running_tests
  • <test_repository_root>/performance_tests
  • <test_repository_root>/hardware

קובצי בדיקה

  • README.md: בכל הבדיקות צריך לכלול תיאור של מטרת הבדיקה והסבר איך להריץ אותה.

  • קובץ בדיקה: לכל הבדיקות יש מבנה דומה:

"""SDV Name Test"""

from sdv_test_fw.test_execution import sdv_base_test, sdv_test_runner

class SdvTypeNameTest(sdv_base_test.SdvBaseTestClass):

    def setup_class(self):
      # Setup code. Executed only once at the beginning of the test.
      super().setup_class()
      self.sdv_device1 = self.get_device('device1')
      self.sdv_device2 = self.get_device('device2')
      ...

    # Remove if not needed.
    def setup_test(self):
      super().setup_test()
      # Setup code. Executed before every test case.
      # Remove override if not needed.

    # Remove if not needed.
    def teardown_test(self):
      # Cleanup code. Executed after every test case.
      super().teardown_test()

    # Remove if not needed.
    def teardown_class(self):
      # Cleanup code. Executed once at the end of the test.
      super().teardown_class()

    def test_name_case1(self):
      # Test case step
      # Test case verification

    def test_name_case2(self):
      # Test case step
      # Test case verification

if __name__ == '__main__':
    # Start Test Execution Using SDV Test Framework
    sdv_test_runner.run()
  • קובץ Build: Android.bp. מבנה הקובץ הוא כזה:
python_test_host {
    name: "SdvTypeNameTest", // Should match the name of the test class.
    main: "sdv_type_name_test.py",
    srcs: [
        "sdv_type_name_test.py",
    ],
    data: [
        ":sdv_test_fw_device_configs",
    ],
    test_options: {
        unit_test: false,
    },
    defaults: [
        "sdv_test_fw_defaults",
    ],
    test_config_template: ":<DEFAULT_TEMPLATE_NAME>",
}

מוסכמה למתן שמות

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

בדיקה לדוגמה

  • שם הקובץ: sdv_sample_<NAME>_test.py

  • שם הכיתה: SdvSampleNameTest

בדיקת E2E

  • שם הקובץ: sdv_e2e_<NAME>_test.py

  • שם הכיתה: SdvE2ENameTest

בדיקה ממושכת

  • שם הקובץ: sdv_long_running_<NAME>_test.py

  • שם הכיתה: SdvLongRunningNameTest

בדיקת ביצועים

  • שם הקובץ: sdv_performance_<NAME>_test.py

  • שם הכיתה: SdvPerformanceNameTest

בדיקת חומרה

  • שם הקובץ: sdv_hw_<NAME>_test.py

  • שם הכיתה: SdvHWNameTest

הנחיות לכתיבת קוד

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

‫Python ו-Mobly

כדאי לעיין במדריך הסגנון של Python ובשיטות המומלצות של Mobly, ולקחת בחשבון את ההמלצות הבאות שספציפיות ל-SDV:

  • לא מומלץ להשתמש בייבוא של Mobly ישירות, למעט הצהרות. ה-SDV Test Framework מבוסס על כך ומתמקד ב-SDV.

  • טענות: אפשר להשתמש ישירות בטענות של Mobly.

בדיקות SDV

בסעיפים הבאים מפורטות הנחיות ספציפיות ושיטות מומלצות לפיתוח בדיקות במסגרת SDV Test Framework.

הגדרה וניקוי

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

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

  1. כדי להפעיל את הפונקציה פעם אחת בלבד, בתחילת הבדיקה ובסופה, משתמשים בתגיות setup_class ו-teardown_class. לדוגמה, אפשר לקבל מכשירים, להגדיר ערכי משתנים או מצב שלא משתנה בין תרחישי בדיקה, להגדיר מאפייני מכשיר נפוצים או להגדיר דגלים.
def setup_class(self):
  super().setup_class()
  # setup code

def teardown_class(self):
  # teardown code
  super().teardown_class()
  1. כדי להריץ בין מקרי בדיקה, לפני כל אחד מהם ואחריו. לדוגמה, סשן אינטראקטיבי או הפעלה של שירות משותף.
def setup_test(self):
  super().setup_test()
  # setup code

def teardown_test(self):
  # teardown code
  super().teardown_test()

מקרי בדיקה עם פרמטרים

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

from absl.testing import parameterized

@parameterized.named_parameters(
  {
      'testcase_name': 'ab',
      'input1': 'a',
      'input2': 'b',
  },
  {
      'testcase_name': 'cd',
      'input1': 'c',
      'input2': 'd',
  },
  )
  def test_name(self, input1, input2):
    # test

בדוגמה נוצרים שני תרחישי בדיקה test_name_ab ו-test_name_cd.

מקרה בדיקה אחד לאימות התנהגות

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

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

דוגמה
test_verify_process():
  device.start_process()

  # precondition 1
  device.send_signal1()
  # verification signal1 received
  ...

  # precondition 2
  device.send_signal2()
  # verification signal2 received
  ...

  # precondition 3
  device.start_agent()
  # verification behavior
  ...

  device.kill_process()
test_setup_test():
  super().setup_test()
  device.start_process()

test_signal1():
  # precondition
  device.send_signal1()
  # verification signal1 received
  ...

test_signal2():
  # precondition
  device.send_signal2()
  # verification signal2 received
  ...

test_agent():
  # precondition
  device.start_agent()
  # verification behavior
  ...

teardown_test():
  device.kill_process()
  super().teardown_test()

התנהגות דטרמיניסטית של בדיקות

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

לא להשתמש בחריגים

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

דוגמה
result = self.some_calculations()
if result is None:
  raise Exception("No result")
result = self.some_calculations()
self.get_test_validator().assert_is_not_none(result)
דוגמה
if not self.device.is_subprocess_running(
  self.EXPECTED_PROCESS
):
  raise Exception("Process is not running")
self.get_test_validator().assert_true(
  self.device.is_subprocess_running(self.EXPECTED_PROCESS),
  "Process is not running"
)

לא להשתמש במצב שינה

לא מומלץ להשתמש ב-sleep כי הוא מאריך את זמן ביצוע הבדיקה וגורם לבעיות.

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

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

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

  1. מהו זמן קצוב סביר לתפוגה?

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

  2. מה העלות של הפעולה שמבוצעת על ידי שיטת ההמתנה?

    מומלץ להימנע מהפעלת פעולות יקרות בתדירות גבוהה. אם צריך, מגדילים את מרווח הזמן בין בדיקות (ברירת המחדל היא 0.5 שניות).

מקרי בדיקה עם דרישות

אם בבדיקה יש תרחישי בדיקה עם דרישות מפורשות להפעלה (לדוגמה, יעד ההפעלה), אפשר לדלג עליהם אם הם לא עומדים בדרישות:

def test_with_requirement():
  self.get_test_validator().skip_if(expr, reason)
  # Test case