Systemtests sind alle SDV-Tests, die mit dem SDV Test Framework erstellt wurden.
Testerstellung
Teststandort
<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
Dateien testen
README.md: Alle Tests müssen eine Beschreibung des Testzwecks und der Ausführung des Tests enthalten.Testdatei: Alle Tests haben eine ähnliche 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()
- Build-Datei:
Android.bp. Die Dateistruktur sieht so aus:
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>",
}
Namenskonvention
Damit die verschiedenen Testtypen identifiziert und gefunden werden können, müssen Tests nach einer bestimmten Namenskonvention erstellt werden:
- Beispieltest:
- Dateiname:
sdv_sample_<NAME>_test.py - Kursname:
SdvSampleNameTest
- Dateiname:
- E2E-Test:
- Dateiname:
sdv_e2e_<NAME>_test.py - Kursname:
SdvE2ENameTest
- Dateiname:
- Langzeittest:
- Dateiname:
sdv_long_running_<NAME>_test.py - Kursname:
SdvLongRunningNameTest
- Dateiname:
- Leistungstest:
- Dateiname:
sdv_performance_<NAME>_test.py - Kursname:
SdvPerformanceNameTest
- Dateiname:
- Hardware test:
- Dateiname:
sdv_hw_<NAME>_test.py - Kursname:
SdvHWNameTest
- Dateiname:
Code-Richtlinien
Dieser Abschnitt enthält Richtlinien und Best Practices für das Schreiben von SDV-Systemtests.
Python und Mobly
Machen Sie sich mit dem Python-Styleguide und den Best Practices für Mobly vertraut und beachten Sie die folgenden SDV-spezifischen Empfehlungen:
Vermeiden Sie die direkte Verwendung von Mobly-Importen, außer für Assertions. Das SDV-Test-Framework baut darauf auf und konzentriert sich auf SDV.
Assertions: Verwenden Sie Mobly-Assertions direkt.
SDV-Tests
In den folgenden Abschnitten finden Sie spezifische Richtlinien und Best Practices für die Entwicklung von Tests im SDV Test Framework.
Einrichten und bereinigen
Der Setup- und Cleanup-Code muss sich außerhalb der Testfälle befinden. Teardown-Methoden werden auch dann aufgerufen, wenn der Test fehlschlägt, um das Gerät ordnungsgemäß zu bereinigen.
Der Speicherort für den Einrichtungs- und Abbaucode hängt von den spezifischen Anforderungen des Tests ab, auch wenn der Test unterbrochen wird:
- Wenn die Funktion nur einmal am Anfang und am Ende des gesamten Tests ausgeführt werden soll, verwenden Sie
setup_classundteardown_class. Sie können beispielsweise Geräte abrufen, Variablenwerte oder Status festlegen, die sich zwischen Testfällen nicht ändern, allgemeine Geräteeigenschaften konfigurieren oder Flags festlegen.
def setup_class(self):
super().setup_class()
# setup code
def teardown_class(self):
# teardown code
super().teardown_class()
- Wird zwischen Testläufen ausgeführt, d. h. vor und nach jedem Testlauf. Beispiel: eine interaktive Sitzung oder eine allgemeine Dienstausführung.
def setup_test(self):
super().setup_test()
# setup code
def teardown_test(self):
# teardown code
super().teardown_test()
Parametrisierte Testläufe
Verwenden Sie parametrisierte Testläufe, wenn die Schritte in verschiedenen Testläufen gleich sind, um Codewiederholungen zu vermeiden.
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
Im Beispiel werden zwei Testläufe erstellt: test_name_ab und test_name_cd.
Ein Testlauf zur Verhaltensprüfung
Testfälle sollten kompakt sein und sich auf ein bestimmtes Verhalten konzentrieren. Wenn mehrere Verhaltensweisen gemeinsame Voraussetzungen oder Schritte haben, sollten Sie sie aufteilen. Sie können setup_test oder die Parametrisierung verwenden, um die Menge an sich wiederholendem Code zu minimieren.
Dieser Ansatz erleichtert das Lesen und Debuggen von Tests, da klar angegeben wird, welche Schritte und Bedingungen fehlgeschlagen sind.
Beispiel
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()
Deterministisches Testverhalten
Fügen Sie dem Test keine Bedingungen hinzu, die sein Verhalten verzweigen. Wenn eine Überprüfung aufgeteilt werden muss, verwende stattdessen zwei verschiedene Testfälle.
Keine Ausnahmen verwenden
In Tests und allgemeinen Hilfsprogrammen müssen Assertions anstelle von Ausnahmen verwendet werden. Dies erleichtert das Debugging und folgt Testmustern.
Beispiel
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)
Beispiel
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"
)
Nicht den Ruhemodus verwenden
Vermeiden Sie die Verwendung von sleep, da dies die Testausführungszeit verlängert und zu Unregelmäßigkeiten führt.
Wenn für den Test auf ein Ereignis oder eine Bestätigung gewartet werden muss, verwende stattdessen die im Framework bereitgestellten Wartemethoden.
Verwenden Sie Wartemethoden mit Bedacht, da die Testausführung blockiert bleibt, bis die Bedingung erfüllt ist oder das Zeitlimit erreicht wird.
Wenn Sie in einem Test auf den Abschluss einer Bedingung warten müssen, stellen Sie sich die folgenden Fragen:
Was ist ein angemessenes Zeitlimit?
Wenn ein Ereignis innerhalb eines bestimmten Zeitraums erwartet wird, sollte das Zeitlimit dieser Erwartung entsprechen, damit der Test schnell fehlschlägt. Reduzieren Sie das Zeitlimit bei Bedarf (der Standardwert ist 30 Sekunden).
Wie teuer ist der Vorgang, der von der Wartemethode ausgeführt wird?
Vermeiden Sie es, häufig rechenintensive Vorgänge aufzurufen. Erhöhen Sie bei Bedarf das Abfrageintervall (Standardwert: 0,5 s).
Testläufe mit Anforderungen
Wenn ein Test Testläufe mit expliziten Anforderungen enthält, die erfüllt sein müssen, damit er funktioniert (z. B. das Ziel, auf dem er ausgeführt werden soll), können Sie diese überspringen, wenn sie die Anforderungen nicht erfüllen:
def test_with_requirement(self):
self.get_test_validator().skip_if(expr, reason)
# Test case