建立 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 系統測試的指南和最佳做法。

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 或參數化,盡量減少重複的程式碼。

採用這種做法,測試會更容易閱讀和偵錯,因為它會清楚指出哪些步驟和條件失敗。

範例
def test_verify_process(self):
  self.device.start_process()

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

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

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

  self.device.kill_process()
def setup_test(self):
  super().setup_test()
  self.device.start_process()

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

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

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

def teardown_test(self):
  self.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):
  self.get_test_validator().skip_if(expr, reason)
  # Test case