Testy systemowe to wszystkie testy SDV utworzone za pomocą platformy testowej SDV.
Tworzenie testu
Lokalizacja testu
<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
Testowanie plików
README.md: wszystkie testy muszą zawierać opis celu testu i sposobu jego uruchomienia.Plik testu: wszystkie testy mają podobną strukturę:
"""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()
- Plik kompilacji:
Android.bp. Struktura pliku jest taka:
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>",
}
Konwencja nazewnictwa
Aby można było identyfikować i znajdować różne typy testów, należy je tworzyć zgodnie z określoną konwencją nazewnictwa.
Przykładowy test
Nazwa pliku:
sdv_sample_<NAME>_test.pyNazwa zajęć:
SdvSampleNameTest
Test E2E
Nazwa pliku:
sdv_e2e_<NAME>_test.pyNazwa zajęć:
SdvE2ENameTest
Test długotrwały
Nazwa pliku:
sdv_long_running_<NAME>_test.pyNazwa zajęć:
SdvLongRunningNameTest
Test wydajności
Nazwa pliku:
sdv_performance_<NAME>_test.pyNazwa zajęć:
SdvPerformanceNameTest
Test sprzętu
Nazwa pliku:
sdv_hw_<NAME>_test.pyNazwa zajęć:
SdvHWNameTest
Wytyczne dotyczące kodu
W tej sekcji znajdziesz wytyczne i sprawdzone metody pisania testów systemowych SDV.
Python i Mobly
Zapoznaj się z przewodnikiem po stylu Pythona i sprawdzonymi metodami Mobly oraz weź pod uwagę te zalecenia dotyczące SDV:
Unikaj bezpośredniego używania importów Mobly, z wyjątkiem asercji. Platforma testowa SDV opiera się na niej i koncentruje się na SDV.
Asercje: używaj bezpośrednio asercji Mobly.
Testy SDV
W kolejnych sekcjach znajdziesz szczegółowe wytyczne i sprawdzone metody tworzenia testów w ramach platformy testowej SDV.
Konfiguracja i czyszczenie
Kod konfiguracji i czyszczenia musi znajdować się poza elementami testowania. Metody teardown są wywoływane nawet wtedy, gdy test się nie powiedzie, aby prawidłowo wyczyścić urządzenie.
Lokalizacja kodu konfiguracji i teardown zależy od konkretnych potrzeb testu, nawet jeśli test zostanie przerwany:
- Aby uruchomić tylko raz na początku i na końcu całego testu, użyj
setup_classiteardown_class. Na przykład pobierz urządzenia, ustaw wartości zmiennych lub stan, który nie zmienia się między elementami testowania, skonfiguruj typowe właściwości urządzenia lub ustaw flagi.
def setup_class(self):
super().setup_class()
# setup code
def teardown_class(self):
# teardown code
super().teardown_class()
- Aby uruchomić między elementami testowania, przed każdym z nich i po każdym z nich. Na przykład sesja interaktywna lub wykonanie wspólnej usługi.
def setup_test(self):
super().setup_test()
# setup code
def teardown_test(self):
# teardown code
super().teardown_test()
Elementy testowania z parametrami
Używaj elementów testowania z parametrami, gdy kroki są wspólne dla różnych elementów testowania, aby uniknąć powtarzania kodu.
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
Ten przykład tworzy 2 elementy testowania: test_name_ab i test_name_cd.
Jeden element testowania do weryfikacji zachowania
Elementy testowania powinny być zwięzłe i skupiać się na jednym konkretnym zachowaniu. Jeśli kilka zachowań ma wspólne warunki wstępne lub kroki, rozważ ich rozdzielenie. Aby zminimalizować ilość powtarzającego się kodu, możesz użyć setup_test lub parametryzacji.
Dzięki temu testy są łatwiejsze do odczytania i debugowania, ponieważ wyraźnie wskazują, które kroki i warunki się nie powiodły.
Przykład
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()
Deterministyczne zachowanie testu
Unikaj dodawania w teście warunków, które rozgałęziają jego zachowanie. Jeśli weryfikację trzeba podzielić, użyj 2 różnych elementów testowania.
Nie używaj wyjątków
Testy i typowe pomocnicze funkcje muszą używać asercji zamiast wyjątków. Ułatwia to debugowanie i jest zgodne z wzorcami testowania.
Przykład
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)
Przykład
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"
)
Nie używaj funkcji sleep
Unikaj używania funkcji sleep, ponieważ zwiększa ona czas wykonywania testu i powoduje niestabilność.
Gdy test wymaga poczekania na zdarzenie lub weryfikację, użyj metod oczekiwania udostępnionych w platformie.
Używaj metod oczekiwania ostrożnie, ponieważ wykonywanie testu pozostaje zablokowane, dopóki warunek nie zostanie spełniony lub nie upłynie limit czasu.
Gdy musisz poczekać, aż warunek zostanie spełniony w teście, zadaj sobie te pytania:
Jaki jest rozsądny limit czasu?
Jeśli zdarzenie ma wystąpić w określonym czasie, limit czasu powinien odpowiadać temu oczekiwaniu, aby test szybko się nie powiódł. W razie potrzeby zmniejsz limit czasu (domyślny to 30 s).
Jak kosztowna jest operacja wykonywana przez metodę oczekiwania?
Unikaj częstego wywoływania kosztownych operacji. W razie potrzeby zwiększ interwał sondowania (domyślny to 0,5 s).
Elementy testowania z wymaganiami
Jeśli test zawiera elementy testowania z wyraźnymi wymaganiami dotyczącymi działania (np. miejsce docelowe, w którym ma być uruchomiony), możesz je pominąć, jeśli nie spełniają wymagań:
def test_with_requirement():
self.get_test_validator().skip_if(expr, reason)
# Test case