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.pyNom de la classe :
SdvSampleNameTest
Test de bout en bout
Nom de fichier :
sdv_e2e_<NAME>_test.pyNom de la classe :
SdvE2ENameTest
Test de longue durée
Nom de fichier :
sdv_long_running_<NAME>_test.pyNom de la classe :
SdvLongRunningNameTest
Test de performance
Nom de fichier :
sdv_performance_<NAME>_test.pyNom de la classe :
SdvPerformanceNameTest
Test matériel
Nom de fichier :
sdv_hw_<NAME>_test.pyNom 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 :
- Pour n'exécuter qu'une seule fois au début et à la fin de l'ensemble du test, utilisez
setup_classetteardown_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()
- 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 :
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).
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