SDV System Test Creation

I test di sistema si riferiscono a qualsiasi test SDV creato utilizzando SDV Test Framework.

Creazione di test

Posizione del test

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

Test dei file

  • README.md: tutti i test devono includere una descrizione dello scopo del test e della procedura per eseguirlo.

  • File di test: tutti i test seguono una struttura simile:

"""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()
  • File di build: Android.bp. La struttura dei file è la seguente:
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>",
}

Convenzione di denominazione

Per identificare e trovare i diversi tipi di test, è necessario crearli seguendo una convenzione di denominazione specifica.

Test di esempio

  • Nome file: sdv_sample_<NAME>_test.py

  • Nome corso: SdvSampleNameTest

Test E2E

  • Nome file: sdv_e2e_<NAME>_test.py

  • Nome corso: SdvE2ENameTest

Test a esecuzione prolungata

  • Nome file: sdv_long_running_<NAME>_test.py

  • Nome corso: SdvLongRunningNameTest

Test di rendimento

  • Nome file: sdv_performance_<NAME>_test.py

  • Nome corso: SdvPerformanceNameTest

Test hardware

  • Nome file: sdv_hw_<NAME>_test.py

  • Nome corso: SdvHWNameTest

Linee guida per il codice

Questa sezione fornisce linee guida e best practice per la scrittura di test di sistema SDV.

Python e Mobly

Acquisisci familiarità con la guida di stile di Python e le best practice di Mobly e tieni in considerazione i seguenti consigli specifici per SDV:

  • Evita di utilizzare direttamente le importazioni di Mobly, ad eccezione delle asserzioni. SDV Test Framework si basa su di esso con un focus su SDV.

  • Asserzioni: utilizza direttamente le asserzioni di Mobly.

Test SDV

Le sezioni seguenti descrivono linee guida e best practice specifiche per lo sviluppo di test all'interno di SDV Test Framework.

Configurazione e pulizia

Il codice di configurazione e pulizia deve essere al di fuori degli scenari di test. I metodi di teardown vengono chiamati anche se il test non riesce, per eseguire una pulizia corretta del dispositivo.

La posizione del codice di configurazione e teardown dipende dalle esigenze specifiche del test, anche se il test viene interrotto:

  1. Per eseguire una sola volta all'inizio e alla fine dell'intero test, utilizza setup_class e teardown_class. Ad esempio, recupera i dispositivi, imposta i valori delle variabili o lo stato che non cambia tra gli scenari di test, configura le proprietà comuni dei dispositivi o imposta i flag.
def setup_class(self):
  super().setup_class()
  # setup code

def teardown_class(self):
  # teardown code
  super().teardown_class()
  1. Per eseguire tra gli scenari di test, prima e dopo ognuno di essi. Ad esempio, una sessione interattiva o l'esecuzione di un servizio comune.
def setup_test(self):
  super().setup_test()
  # setup code

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

Scenari di test con parametri

Utilizza scenari di test con parametri quando i passaggi sono comuni a diversi scenari di test per evitare la ripetizione del codice.

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

L'esempio crea due scenari di test test_name_ab e test_name_cd.

Uno scenario di test per la verifica del comportamento

Gli scenari di test devono essere compatti e concentrarsi su un comportamento specifico. Se più comportamenti condividono precondizioni o passaggi comuni, valuta la possibilità di suddividerli. Puoi utilizzare setup_test o la parametrizzazione per ridurre al minimo la quantità di codice ripetitivo.

Seguendo questo approccio, i test sono più facili da leggere e sottoporre a debug perché indica chiaramente quali passaggi e condizioni non sono riusciti.

Esempio
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()

Comportamento deterministico dei test

Evita di aggiungere istruzioni condizionali nel test che ramificano il suo comportamento. Se è necessario suddividere una verifica, utilizza invece due scenari di test diversi.

Non utilizzare le eccezioni

I test e gli helper comuni devono utilizzare le asserzioni anziché le eccezioni. In questo modo si facilita il debug e si seguono i pattern di test.

Esempio
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)
Esempio
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"
)

Non utilizzare sleep()

Evita di utilizzare sleep() perché aumenta il tempo di esecuzione dei test e causa instabilità.

Quando il test richiede l'attesa di un evento o di una verifica per continuare, utilizza invece i metodi di attesa forniti nel framework.

Utilizza i metodi di attesa con attenzione perché l'esecuzione del test rimane bloccata finché la condizione non corrisponde o non viene raggiunto il timeout.

Quando devi attendere il completamento di una condizione in un test, poni le seguenti domande:

  1. Qual è un timeout ragionevole?

    Se è previsto un evento entro un periodo di tempo specifico, il timeout deve corrispondere a questa previsione per assicurarsi che il test non riesca rapidamente. Se necessario, riduci il timeout (il valore predefinito è 30 secondi).

  2. Quanto è costosa l'operazione eseguita dal metodo di attesa?

    Evita di chiamare spesso operazioni costose. Se necessario, aumenta l'intervallo di polling (il valore predefinito è 0,5 secondi).

Scenari di test con requisiti

Se un test ha scenari di test con requisiti espliciti per il funzionamento (ad esempio, il target in cui deve essere eseguito), puoi saltarli se non soddisfano i requisiti:

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