Создание системного теста SDV

Системные тесты – это любые тесты SDV, созданные с помощью SDV Test Framework.

Создание теста

Тестовое местоположение

  • <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 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 или параметризацию.

Такой подход упрощает чтение и отладку тестов, поскольку он четко указывает, какие шаги и условия не выполнены.

Пример
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