Tworzenie testu systemu SDV

Testy systemu to dowolne testy SDV utworzone za pomocą platformy testowej SDV.

Tworzenie testu

Lokalizacja testowa

  • <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

Testowanie plików

  • README.md: wszystkie testy muszą zawierać opis celu testu i sposobu jego przeprowadzenia.

  • Plik testowy: wszystkie testy mają podobną strukturę:

"""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()
  • Plik kompilacji: Android.bp. Struktura pliku jest następująca:
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>",
}

Konwencja nazewnictwa

Aby można było zidentyfikować i znaleźć różne typy testów, należy je tworzyć zgodnie z określoną konwencją nazewnictwa:

  • Przykładowy test:
    • Nazwa pliku: sdv_sample_<NAME>_test.py
    • Nazwa zajęć: SdvSampleNameTest
  • Test E2E:
    • Nazwa pliku: sdv_e2e_<NAME>_test.py
    • Nazwa zajęć: SdvE2ENameTest
  • Długotrwały test:
    • Nazwa pliku: sdv_long_running_<NAME>_test.py
    • Nazwa zajęć: SdvLongRunningNameTest
  • Test wydajności:
    • Nazwa pliku: sdv_performance_<NAME>_test.py
    • Nazwa zajęć: SdvPerformanceNameTest
  • Test sprzętu:
    • Nazwa pliku: sdv_hw_<NAME>_test.py
    • Nazwa zajęć: SdvHWNameTest

Wskazówki dotyczące kodu

W tej sekcji znajdziesz wskazówki i sprawdzone metody dotyczące pisania testów systemu SDV.

Python i Mobly

Zapoznaj się z przewodnikiem po stylu Pythona i sprawdzonymi metodami dotyczącymi Mobly oraz weź pod uwagę te zalecenia dotyczące SDV:

  • Unikaj bezpośredniego używania importów Mobly, z wyjątkiem asercji. Platforma testowa SDV opiera się na nim, koncentrując się na SDV.

  • Asercje: używaj asercji Mobly bezpośrednio.

Testy SDV

W poniższych sekcjach znajdziesz szczegółowe wytyczne i sprawdzone metody tworzenia testów w ramach SDV Test Framework.

Konfiguracja i czyszczenie

Kod konfiguracji i czyszczenia musi znajdować się poza elementami testowania. Metody teardown są wywoływane nawet wtedy, gdy test się nie powiedzie, aby prawidłowo wyczyścić urządzenie.

Lokalizacja kodu konfiguracji i demontażu zależy od konkretnych potrzeb testu, nawet jeśli zostanie on przerwany:

  1. Aby uruchomić tylko raz na początku i na końcu całego testu, użyj setup_class i teardown_class. Możesz na przykład pobrać urządzenia, ustawić wartości zmiennych lub stan, który nie zmienia się między elementami testowania, skonfigurować wspólne właściwości urządzeń lub ustawić flagi.
def setup_class(self):
  super().setup_class()
  # setup code

def teardown_class(self):
  # teardown code
  super().teardown_class()
  1. Do uruchamiania między elementami testowania, przed i po każdym z nich. Może to być na przykład sesja interaktywna lub wykonanie wspólnej usługi.
def setup_test(self):
  super().setup_test()
  # setup code

def teardown_test(self):
  # teardown code
  super().teardown_test()

Elementy testowania z parametrami

Aby uniknąć powtarzania kodu, używaj sparametryzowanych elementów testowania, gdy kroki są wspólne dla różnych elementów testowania.

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

W tym przykładzie tworzone są 2 elementy testowania: test_name_ab i test_name_cd.

1 element testowania do weryfikacji zachowania.

Elementy testowania powinny być zwarte i skupiać się na jednym konkretnym zachowaniu. Jeśli wiele zachowań ma wspólne warunki wstępne lub kroki, rozważ ich rozdzielenie. Aby zminimalizować ilość powtarzającego się kodu, możesz użyć setup_test lub parametryzacji.

Dzięki temu testy są łatwiejsze do odczytania i debugowania, ponieważ wyraźnie wskazują, które kroki i warunki nie zostały spełnione.

Przykład
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()

Deterministyczne działanie testu

Unikaj dodawania w teście warunków, które rozgałęziają jego działanie. Jeśli weryfikację trzeba podzielić, użyj 2 różnych elementów testowania.

Nie używaj wyjątków

Testy i typowe funkcje pomocnicze muszą używać asercji zamiast wyjątków. Ułatwia to debugowanie i jest zgodne ze wzorcami testowania.

Przykład
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)
Przykład
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"
)

Nie używaj trybu uśpienia

Unikaj używania sleep, ponieważ wydłuża to czas wykonywania testu i powoduje niestabilność.

Jeśli test wymaga oczekiwania na zdarzenie lub weryfikację, użyj metod oczekiwania dostępnych w platformie.

Ostrożnie używaj metod oczekiwania, ponieważ wykonanie testu pozostaje zablokowane, dopóki warunek nie zostanie spełniony lub nie upłynie limit czasu.

Jeśli musisz poczekać na spełnienie warunku w teście, zadaj sobie te pytania:

  1. Jaki jest rozsądny limit czasu?

    Jeśli zdarzenie jest oczekiwane w określonym przedziale czasu, limit czasu powinien odpowiadać temu oczekiwaniu, aby test szybko się nie powiódł. W razie potrzeby skróć limit czasu (domyślnie jest to 30 sekund).

  2. Jak kosztowna jest operacja wykonywana przez metodę oczekiwania?

    Unikaj częstego wywoływania kosztownych operacji. W razie potrzeby zwiększ interwał sondowania (domyślnie wynosi on 0,5 s).

Elementy testowania z wymaganiami

Jeśli test zawiera elementy testowania z wyraźnymi wymaganiami dotyczącymi działania (np. miejsce docelowe, w którym powinien zostać uruchomiony), możesz je pominąć, jeśli nie spełniają wymagań:

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