SDV-Systemtest erstellen

Systemtests sind alle SDV-Tests, die mit dem SDV Test Framework erstellt wurden.

Tests erstellen

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 eine Anleitung zum Ausführen 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 Arten von Tests identifiziert und gefunden werden können, müssen sie einer bestimmten Namenskonvention folgen.

Beispieltest

  • Dateiname: sdv_sample_<NAME>_test.py

  • Kursname: SdvSampleNameTest

E2E-Test

  • Dateiname: sdv_e2e_<NAME>_test.py

  • Kursname: SdvE2ENameTest

Test mit langer Ausführungszeit

  • Dateiname: sdv_long_running_<NAME>_test.py

  • Kursname: SdvLongRunningNameTest

Leistungstest

  • Dateiname: sdv_performance_<NAME>_test.py

  • Kursname: SdvPerformanceNameTest

Hardwaretest

  • Dateiname: sdv_hw_<NAME>_test.py

  • Kursname: SdvHWNameTest

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 Mobly-Best Practices vertraut und berücksichtigen Sie die folgenden SDV-spezifischen Empfehlungen:

  • Verwenden Sie Mobly-Importe nur für Zusicherungen. Das SDV Test Framework baut darauf auf und konzentriert sich auf SDV.

  • Assertions:Verwenden Sie Mobly-Assertions direkt.

SDV-Tests

In den folgenden Abschnitten werden spezifische Richtlinien und Best Practices für die Entwicklung von Tests im SDV Test Framework beschrieben.

Einrichten und Bereinigen

Setup- und Bereinigungscode müssen sich außerhalb der Testläufe befinden. Teardown-Methoden werden auch dann aufgerufen, wenn der Test fehlschlägt, um das Gerät ordnungsgemäß zu bereinigen.

Der Speicherort für Setup- und Teardown-Code hängt von den spezifischen Anforderungen des Tests ab, auch wenn der Test unterbrochen wird:

  1. Wenn Sie nur einmal am Anfang und Ende des gesamten Tests ausgeführt werden soll, verwenden Sie setup_class und teardown_class. Sie können beispielsweise Geräte abrufen, Variablenwerte oder einen Status festlegen, der sich zwischen Testläufen nicht ändert, 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()
  1. Zwischen Testläufen ausführen, vor und nach jedem Testlauf. Beispiele sind eine interaktive Sitzung oder die Ausführung eines gemeinsamen Dienstes.
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 Code zu vermeiden, der sich wiederholt.

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 Verhaltensüberprüfung

Testläufe 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 macht Tests leichter lesbar und einfacher zu debuggen, da er deutlich angibt, bei welchen Schritten und Bedingungen Fehler aufgetreten sind.

Beispiel
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()

Deterministisches Testverhalten

Fügen Sie im Test keine Bedingungen hinzu, die das Verhalten verzweigen. Wenn eine Überprüfung aufgeteilt werden muss, verwenden Sie stattdessen zwei verschiedene Testläufe.

Keine Ausnahmen verwenden

In Tests und allgemeinen Helfern müssen Zusicherungen anstelle von Ausnahmen verwendet werden. Das erleichtert das Debugging und entspricht den 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"
)

sleep() nicht verwenden

Vermeiden Sie die Verwendung von sleep(), da dadurch die Testausführungszeit verlängert wird und es zu Instabilität kommen kann.

Wenn für den Test gewartet werden muss, bis ein Ereignis eintritt oder eine Bestätigung erfolgt, verwenden Sie stattdessen die im Framework bereitgestellten Wartemethoden.

Wartemethoden sollten mit Bedacht eingesetzt werden, da die Testausführung blockiert bleibt, bis die Bedingung erfüllt ist oder das Zeitlimit erreicht wird.

Wenn Sie in einem Test warten müssen, bis eine Bedingung abgeschlossen ist, stellen Sie sich die folgenden Fragen:

  1. Was ist ein angemessenes Zeitlimit?

    Wenn ein Ereignis innerhalb eines bestimmten Zeitrahmens erwartet wird, sollte das Zeitlimit dieser Erwartung entsprechen, damit der Test schnell fehlschlägt. Reduzieren Sie bei Bedarf das Zeitlimit (der Standardwert ist 30 Sekunden).

  2. Wie teuer ist der Vorgang, der von der Wartemethode ausgeführt wird?

    Rufen Sie leistungsintensive Vorgänge nicht zu oft auf. Erhöhen Sie bei Bedarf das Abfrageintervall (der Standardwert ist 0,5 Sekunden).

Testläufe mit Anforderungen

Wenn ein Test Testläufe mit expliziten Anforderungen hat (z. B. das Ziel, auf dem er ausgeführt werden soll), können Sie diese überspringen, wenn sie nicht den Anforderungen entsprechen:

def test_with_requirement():
  self.get_test_validator().skip_if(expr, reason)
  # Test case