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 或参数化来最大限度地减少重复代码。

采用这种方法可以更轻松地阅读和调试测试,因为它会清楚地指明哪些步骤和条件失败了。

示例
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