ایجاد تست سیستم 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

آزمون 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 ارائه می‌دهد.

پایتون و موبلی

با راهنمای سبک پایتون و بهترین شیوه‌های Mobly آشنا شوید و توصیه‌های خاص SDV زیر را در نظر بگیرید:

  • از استفاده مستقیم از ایمپورت‌های Mobly به جز برای assertionها خودداری کنید. چارچوب تست SDV بر اساس آن و با تمرکز بر SDV ساخته شده است.

  • ادعاها: مستقیماً از ادعاهای Mobly استفاده کنید.

آزمایش‌های SDV

بخش‌های زیر دستورالعمل‌های خاص و بهترین شیوه‌ها را برای توسعه تست‌ها در چارچوب تست SDV تشریح می‌کنند.

راه‌اندازی و پاکسازی

کد راه‌اندازی و پاکسازی باید خارج از موارد آزمایشی باشد. متدهای Teardown حتی اگر آزمایش با شکست مواجه شود، فراخوانی می‌شوند تا پاکسازی مناسب دستگاه انجام شود.

محل تنظیم و باز کردن کد به نیازهای خاص آزمایش بستگی دارد، حتی اگر آزمایش قطع شود:

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

رفتار آزمون قطعی

از اضافه کردن شرط‌هایی که باعث شاخه شاخه شدن رفتار تست می‌شوند، خودداری کنید. اگر یک تأیید نیاز به تقسیم شدن دارد، به جای آن از دو مورد تست مختلف استفاده کنید.

از استثنائات استفاده نکنید

تست‌ها و کمک‌کننده‌های رایج باید به جای استثناها از ادعاها (assertions) استفاده کنند. این کار اشکال‌زدایی را تسهیل می‌کند و از الگوهای تست پیروی می‌کند.

مثال
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