Создание системных тестов 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 и Mobile

Ознакомьтесь с руководством по стилю 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()

Детерминированное поведение при тестировании

Избегайте добавления в тест условных операторов, которые разветвляют его поведение. Если проверку необходимо разделить, используйте вместо этого два разных тестовых случая.

Не используйте исключения.

В тестах и ​​вспомогательных функциях следует использовать утверждения (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 поскольку она увеличивает время выполнения тестов и вызывает нестабильность.

Если для продолжения теста требуется ожидание события или проверки, используйте вместо этого методы ожидания, предоставляемые фреймворком.

Используйте методы ожидания с осторожностью, поскольку выполнение теста остается заблокированным до тех пор, пока не будет выполнено условие или не истечет время ожидания.

Когда вам нужно дождаться завершения выполнения условия в тесте, задайте себе следующие вопросы:

  1. Какое значение тайм-аута можно считать разумным?

    Если событие ожидается в течение определенного промежутка времени, время ожидания должно соответствовать этому ожиданию, чтобы тест быстро завершился с ошибкой. При необходимости уменьшите время ожидания (по умолчанию — 30 секунд).

  2. Насколько дорогостоящей является операция, выполняемая методом ожидания?

    Избегайте частого вызова ресурсоемких операций. При необходимости увеличьте интервал опроса (по умолчанию — 0,5 с).

Тестовые примеры с требованиями

Если тест содержит тестовые случаи с явно заданными требованиями к выполнению (например, целевая платформа, где он должен выполняться), вы можете пропустить их, если они не соответствуют этим требованиям:

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