إنشاء اختبار نظام 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()
  • ملف الإصدار: 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

اختبار شامل

  • اسم الملف: 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 إلى ذلك مع التركيز على SDV.

  • التأكيدات: استخدِم تأكيدات Mobly مباشرةً.

اختبارات SDV

توضّح الأقسام التالية إرشادات وأفضل الممارسات المحدّدة لتطوير الاختبارات ضمن إطار عمل اختبار SDV.

الإعداد والتنظيف

يجب أن يكون رمز الإعداد والتنظيف خارج أُطُر الاختبار. يتم استدعاء طرق الإيقاف حتى إذا فشل الاختبار، وذلك لتنظيف الجهاز بشكلٍ صحيح.

يعتمد موقع رمز الإعداد والإيقاف على الاحتياجات المحدّدة للاختبار، حتى إذا تم إيقافه:

  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()

تجنَّب استخدام sleep() لأنّه يزيد من وقت تنفيذ الاختبار ويؤدي إلى عدم استقراره.

عندما يتطلب الاختبار انتظار حدث أو عملية تحقّق لمواصلة التنفيذ، استخدِم طرق الانتظار المتوفّرة في إطار العمل بدلاً من ذلك.

استخدِم طرق الانتظار بعناية لأنّ تنفيذ الاختبار يظل محظورًا إلى أن يتطابق الشرط أو يتم بلوغ المهلة.

عندما تحتاج إلى انتظار اكتمال شرط في اختبار، اطرح الأسئلة التالية:

  1. ما هي المهلة المعقولة؟

    إذا كان من المتوقّع حدوث حدث خلال إطار زمني محدّد، يجب أن تتطابق المهلة مع هذا التوقّع للتأكّد من فشل الاختبار بسرعة. قلِّل المهلة إذا لزم الأمر (المهلة التلقائية هي 30 ثانية).

  2. ما مدى تكلفة العملية التي تنفّذها طريقة الانتظار؟

    تجنَّب استدعاء العمليات المكلّفة بشكل متكرّر. زِد فترة الاقتراع إذا لزم الأمر (الفترة التلقائية هي 0.5 ثانية).

أُطُر الاختبار التي تتضمّن متطلبات

إذا كان الاختبار يتضمّن أُطُر اختبار لها متطلبات صريحة لكي تعمل (على سبيل المثال، الهدف الذي يجب أن يتم تشغيلها عليه)، يمكنك تخطّيها إذا لم تتطابق مع المتطلبات:

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