Le verifiche del sistema si riferiscono a qualsiasi test SDV creato utilizzando il framework di test SDV.
Creazione di test
Località di 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 di come 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 del 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, questi devono essere creati seguendo una convenzione di denominazione specifica:
- Test di esempio:
- Nome file:
sdv_sample_<NAME>_test.py - Nome del corso:
SdvSampleNameTest
- Nome file:
- Test E2E:
- Nome file:
sdv_e2e_<NAME>_test.py - Nome del corso:
SdvE2ENameTest
- Nome file:
- Test a lunga esecuzione:
- Nome file:
sdv_long_running_<NAME>_test.py - Nome del corso:
SdvLongRunningNameTest
- Nome file:
- Test delle prestazioni:
- Nome file:
sdv_performance_<NAME>_test.py - Nome del corso:
SdvPerformanceNameTest
- Nome file:
- Test dell'hardware:
- Nome file:
sdv_hw_<NAME>_test.py - Nome del corso:
SdvHWNameTest
- Nome file:
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 considera i seguenti consigli specifici per gli SDV:
Evita di utilizzare direttamente le importazioni Mobly, ad eccezione delle asserzioni. Il framework di test SDV si basa su questo, con un focus su SDV.
Asserzioni: utilizza direttamente le asserzioni Mobly.
Test SDV
Le sezioni seguenti descrivono linee guida e best practice specifiche per lo sviluppo di test all'interno del framework di test SDV.
Configurazione e pulizia
Il codice di configurazione e cleanup deve essere esterno agli scenari di test. I metodi di teardown vengono chiamati anche se il test non va a buon fine, per eseguire una corretta pulizia del dispositivo.
La posizione del codice di configurazione e di chiusura dipende dalle esigenze specifiche del test, anche se il test viene interrotto:
- Per l'esecuzione una sola volta all'inizio e alla fine dell'intero test, utilizza
setup_classeteardown_class. Ad esempio, recuperare i dispositivi, impostare i valori delle variabili o lo stato che non cambia tra gli scenari di test, configurare le proprietà comuni dei dispositivi o impostare i flag.
def setup_class(self):
super().setup_class()
# setup code
def teardown_class(self):
# teardown code
super().teardown_class()
- Da eseguire tra gli scenari di test, prima e dopo ciascuno 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 parametrizzati
Utilizza scenari di test parametrizzati 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.
Questo approccio semplifica la lettura e il debug dei test perché indica chiaramente quali passaggi e condizioni non sono riusciti.
Esempio
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()
Comportamento di test deterministico
Evita di aggiungere condizioni nel test che ne ramificano il comportamento. Se una verifica deve essere suddivisa, utilizza 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, il debug è più semplice e vengono seguiti 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 la modalità di sospensione
Evita di utilizzare sleep perché aumenta il tempo di esecuzione del 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, poniti le seguenti domande:
Qual è un timeout ragionevole?
Se un evento è previsto entro un periodo di tempo specifico, il timeout deve corrispondere a questa aspettativa per garantire che il test non vada a buon fine rapidamente. Se necessario, riduci il timeout (il valore predefinito è 30 s).
Quanto è costosa l'operazione eseguita dal metodo in attesa?
Evita di chiamare spesso operazioni costose. Se necessario, aumenta l'intervallo di polling (il valore predefinito è 0,5 s).
Scenari di test con requisiti
Se un test contiene scenari di test con requisiti espliciti per funzionare (ad esempio, la destinazione in cui deve essere eseguito), puoi ignorarli se non corrispondono ai requisiti:
def test_with_requirement(self):
self.get_test_validator().skip_if(expr, reason)
# Test case