Création de tests du système SDV

Les tests système font référence à tout test SDV créé à l'aide du framework de test SDV.

Création de tests

Emplacement des tests

  • <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 leur objectif et de la manière de les 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, ils doivent être créés en suivant une convention d'attribution de noms spécifique.

Exemple de test

  • Nom de fichier : sdv_sample_<NAME>_test.py

  • Nom de la classe : SdvSampleNameTest

Test de bout en bout

  • Nom de fichier : sdv_e2e_<NAME>_test.py

  • Nom de la classe : SdvE2ENameTest

Test de longue durée

  • Nom de fichier : sdv_long_running_<NAME>_test.py

  • Nom de la classe : SdvLongRunningNameTest

Test de performance

  • Nom de fichier : sdv_performance_<NAME>_test.py

  • Nom de la classe : SdvPerformanceNameTest

Test matériel

  • Nom de fichier : sdv_hw_<NAME>_test.py

  • Nom de la classe : SdvHWNameTest

Consignes de codage

Cette section fournit des consignes et des bonnes pratiques pour écrire des 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 spécifiques à SDV suivantes :

  • Évitez d'utiliser directement les importations Mobly, à l'exception des assertions. Le framework de test SDV s'appuie sur celui-ci en se concentrant sur SDV.

  • Assertions : utilisez directement les assertions Mobly.

Tests SDV

Les sections suivantes décrivent des consignes et des 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 suppression 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 s'il est interrompu :

  1. Pour n'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, obtenez des appareils, définissez des valeurs de variables ou un état qui ne change pas entre les scénarios de test, configurez des propriétés d'appareil communes ou définissez des indicateurs.
def setup_class(self):
  super().setup_class()
  # setup code

def teardown_class(self):
  # teardown code
  super().teardown_class()
  1. Pour exécuter entre les scénarios de test, avant et après chacun d'eux. Par exemple, une session interactive ou une exécution de service commune.
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 la paramétrisation 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
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()

Comportement déterministe des tests

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

N'utilisez pas d'exceptions

Les tests et les assistants communs 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"
)

N'utilisez pas de veille

Évitez d'utiliser sleep, car cela augmente le temps d'exécution des tests et provoque une instabilité.

Lorsque le test nécessite d'attendre la poursuite d'un événement ou d'une validation, 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 de test reste bloquée jusqu'à ce que la condition corresponde ou que le délai d'inactivité soit atteint.

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

  1. Quel est un délai d'inactivité raisonnable ?

    Si un événement est attendu dans un délai spécifique, le délai d'inactivité doit correspondre à cette attente pour s'assurer que le test échoue rapidement. Réduisez le délai d'inactivité 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 seconde).

Scénarios de test avec des exigences

Si un test comporte des scénarios de test avec des exigences explicites (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.get_test_validator().skip_if(expr, reason)
  # Test case