Création de tests du système SDV

Les tests système désignent tous les tests SDV créés à l'aide du framework de test SDV.

Création de tests

Lieu du 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

Tester des fichiers

  • README.md : tous les tests doivent inclure une description de l'objectif du test et de la manière de l'exécuter.

  • Fichier de test : tous les tests suivent une structure similaire :

"""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()
  • Fichier de compilation : Android.bp. La structure du fichier est la suivante :
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>",
}

Convention d'attribution de noms

Pour identifier et trouver les différents types de tests, ceux-ci doivent être créés selon une convention d'attribution de noms spécifique :

  • Exemple de test :
    • Nom du fichier : sdv_sample_<NAME>_test.py
    • Nom de la classe : SdvSampleNameTest
  • Test E2E :
    • Nom du fichier : sdv_e2e_<NAME>_test.py
    • Nom de la classe : SdvE2ENameTest
  • Test de longue durée :
    • Nom du fichier : sdv_long_running_<NAME>_test.py
    • Nom de la classe : SdvLongRunningNameTest
  • Test de performances :
    • Nom du fichier : sdv_performance_<NAME>_test.py
    • Nom de la classe : SdvPerformanceNameTest
  • Test du matériel :
    • Nom du fichier : sdv_hw_<NAME>_test.py
    • Nom de la classe : SdvHWNameTest

Consignes de codage

Cette section fournit des consignes et des bonnes pratiques pour l'écriture de tests système SDV.

Python et Mobly

Familiarisez-vous avec le guide de style Python et les bonnes pratiques Mobly, et tenez compte des recommandations suivantes spécifiques aux SDV :

  • Évitez d'utiliser directement les importations Mobly, sauf pour les assertions. Le framework de test SDV s'appuie sur ce framework, en mettant l'accent sur le SDV.

  • Assertions : utilisez directement les assertions Mobly.

Tests SDV

Les sections suivantes décrivent les consignes et les bonnes pratiques spécifiques pour développer des tests dans le framework de test SDV.

Configuration et nettoyage

Le code de configuration et de nettoyage doit se trouver en dehors des scénarios de test. Les méthodes de démontage sont appelées même si le test échoue, afin de nettoyer correctement l'appareil.

L'emplacement du code de configuration et de suppression dépend des besoins spécifiques du test, même si celui-ci est interrompu :

  1. Pour ne l'exécuter qu'une seule fois au début et à la fin de l'ensemble du test, utilisez setup_class et teardown_class. Par exemple, obtenir des appareils, définir des valeurs de variables ou un état qui ne change pas entre les scénarios de test, configurer des propriétés d'appareil courantes ou définir des indicateurs.
def setup_class(self):
  super().setup_class()
  # setup code

def teardown_class(self):
  # teardown code
  super().teardown_class()
  1. Pour s'exécuter entre les scénarios de test, avant et après chacun d'eux. Par exemple, une session interactive ou l'exécution d'un service commun.
def setup_test(self):
  super().setup_test()
  # setup code

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

Scénarios de test paramétrés

Utilisez des scénarios de test paramétrés lorsque les étapes sont communes à différents scénarios de test pour éviter la répétition du code.

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'exemple crée deux scénarios de test, test_name_ab et test_name_cd.

Un scénario de test pour la vérification du comportement

Les scénarios de test doivent être compacts et se concentrer sur un comportement spécifique. Si plusieurs comportements partagent des conditions préalables ou des étapes communes, envisagez de les diviser. Vous pouvez utiliser setup_test ou le paramétrage pour réduire la quantité de code répétitif.

Cette approche facilite la lecture et le débogage des tests, car elle indique clairement les étapes et les conditions qui ont échoué.

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

Comportement de test déterministe

Évitez d'ajouter des conditions dans le test qui modifient son comportement. Si une validation doit être divisée, utilisez plutôt deux scénarios de test différents.

Ne pas utiliser d'exceptions

Les tests et les assistants courants doivent utiliser des assertions au lieu d'exceptions. Cela facilite le débogage et suit les modèles de test.

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

Ne pas utiliser la mise en veille

Évitez d'utiliser sleep, car cela augmente le temps d'exécution des tests et entraîne des instabilités.

Lorsque le test nécessite d'attendre un événement ou une validation pour continuer, utilisez plutôt les méthodes d'attente fournies dans le framework.

Utilisez les méthodes d'attente avec précaution, car l'exécution du test reste bloquée jusqu'à ce que la condition soit remplie ou que le délai avant expiration soit atteint.

Lorsque vous devez attendre qu'une condition soit remplie dans un test, posez-vous les questions suivantes :

  1. Qu'est-ce qu'un délai d'expiration raisonnable ?

    Si un événement est attendu dans un délai spécifique, le délai avant expiration doit correspondre à cette attente pour s'assurer que le test échoue rapidement. Réduisez le délai avant expiration si nécessaire (la valeur par défaut est de 30 secondes).

  2. Quel est le coût de l'opération effectuée par la méthode d'attente ?

    Évitez d'appeler fréquemment des opérations coûteuses. Augmentez l'intervalle d'interrogation si nécessaire (la valeur par défaut est de 0,5 s).

Scénarios de test avec exigences

Si un test comporte des scénarios de test avec des exigences explicites pour fonctionner (par exemple, la cible sur laquelle il doit s'exécuter), vous pouvez les ignorer s'ils ne correspondent pas aux exigences :

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