การสร้างการทดสอบระบบ SDV

การทดสอบระบบหมายถึงการทดสอบ SDV ที่สร้างขึ้นโดยใช้เฟรมเวิร์กการทดสอบ SDV

การสร้างการทดสอบ

สถานที่ทดสอบ

  • <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

ทดสอบไฟล์

  • README.md: การทดสอบทั้งหมดต้องมีคำอธิบายวัตถุประสงค์ของการทดสอบและวิธีเรียกใช้การทดสอบ

  • ไฟล์ทดสอบ: การทดสอบทั้งหมดมีโครงสร้างที่คล้ายกัน ดังนี้

"""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()
  • ไฟล์บิลด์: Android.bp โครงสร้างไฟล์มีดังนี้
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>",
}

รูปแบบการตั้งชื่อ

หากต้องการระบุและค้นหาการทดสอบประเภทต่างๆ คุณต้องสร้างการทดสอบตามรูปแบบการตั้งชื่อที่เฉพาะเจาะจง ดังนี้

  • ตัวอย่างการทดสอบ
    • ชื่อไฟล์: sdv_sample_<NAME>_test.py
    • ชื่อชั้นเรียน: SdvSampleNameTest
  • การทดสอบ E2E
    • ชื่อไฟล์: sdv_e2e_<NAME>_test.py
    • ชื่อชั้นเรียน: SdvE2ENameTest
  • การทดสอบที่ใช้เวลานาน:
    • ชื่อไฟล์: sdv_long_running_<NAME>_test.py
    • ชื่อชั้นเรียน: SdvLongRunningNameTest
  • การทดสอบประสิทธิภาพ
    • ชื่อไฟล์: sdv_performance_<NAME>_test.py
    • ชื่อชั้นเรียน: SdvPerformanceNameTest
  • การทดสอบฮาร์ดแวร์
    • ชื่อไฟล์: sdv_hw_<NAME>_test.py
    • ชื่อชั้นเรียน: SdvHWNameTest

หลักเกณฑ์เกี่ยวกับโค้ด

ส่วนนี้จะให้หลักเกณฑ์และแนวทางปฏิบัติแนะนำสำหรับการเขียนการทดสอบระบบ SDV

Python และ Mobly

ทำความคุ้นเคยกับคู่มือสไตล์ Python และแนวทางปฏิบัติแนะนำของ Mobly และพิจารณาคำแนะนำเฉพาะสำหรับ SDV ต่อไปนี้

  • หลีกเลี่ยงการใช้การนำเข้า Mobly โดยตรง ยกเว้นการยืนยัน เฟรมเวิร์กการทดสอบ SDV สร้างขึ้นจากเฟรมเวิร์กดังกล่าวโดยมุ่งเน้นที่ SDV

  • การยืนยัน: ใช้การยืนยัน Mobly โดยตรง

การทดสอบ SDV

ส่วนต่อไปนี้จะอธิบายหลักเกณฑ์และแนวทางปฏิบัติแนะนำเฉพาะสำหรับการพัฒนาการทดสอบภายในเฟรมเวิร์กการทดสอบ SDV

การตั้งค่าและการล้างข้อมูล

โค้ดการตั้งค่าและการล้างต้องอยู่นอกกรอบการทดสอบ ระบบจะเรียกใช้เมธอดการแยกส่วนแม้ว่าการทดสอบจะล้มเหลว เพื่อล้างข้อมูลอุปกรณ์อย่างเหมาะสม

ตำแหน่งของโค้ดการตั้งค่าและการแยกส่วนจะขึ้นอยู่กับความต้องการเฉพาะของการทดสอบ แม้ว่าการทดสอบจะถูกขัดจังหวะก็ตาม

  1. หากต้องการเรียกใช้เพียงครั้งเดียวที่จุดเริ่มต้นและจุดสิ้นสุดของการทดสอบทั้งหมด ให้ใช้ setup_class และ teardown_class เช่น รับอุปกรณ์ ตั้งค่าตัวแปรหรือสถานะที่ไม่เปลี่ยนแปลงระหว่างกรอบการทดสอบ กำหนดค่าพร็อพเพอร์ตี้อุปกรณ์ทั่วไป หรือตั้งค่าแฟล็ก
def setup_class(self):
  super().setup_class()
  # setup code

def teardown_class(self):
  # teardown code
  super().teardown_class()
  1. เพื่อเรียกใช้ระหว่างกรอบการทดสอบ ก่อนและหลังแต่ละกรอบการทดสอบ เช่น เซสชันแบบอินเทอร์แอกทีฟหรือการดำเนินการบริการทั่วไป
def setup_test(self):
  super().setup_test()
  # setup code

def teardown_test(self):
  # teardown code
  super().teardown_test()

กรอบการทดสอบที่มีพารามิเตอร์

ใช้กรอบการทดสอบที่มีพารามิเตอร์เมื่อขั้นตอนต่างๆ เหมือนกันในกรอบการทดสอบที่แตกต่างกันเพื่อหลีกเลี่ยงการเขียนโค้ดซ้ำ

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

ตัวอย่างนี้จะสร้างกรอบการทดสอบ 2 รายการ ได้แก่ test_name_ab และ test_name_cd

กรอบการทดสอบ 1 รายการสำหรับการยืนยันลักษณะการทำงาน

กรอบการทดสอบควรกระชับและมุ่งเน้นที่ลักษณะการทำงานที่เฉพาะเจาะจงรายการเดียว หากพฤติกรรมหลายอย่างมีเงื่อนไขเบื้องต้นหรือขั้นตอนที่เหมือนกัน ให้พิจารณาแยกพฤติกรรมเหล่านั้นออกจากกัน คุณสามารถใช้ setup_test หรือการกำหนดพารามิเตอร์เพื่อลดจำนวนโค้ดที่ซ้ำกันได้

การทำตามแนวทางนี้จะช่วยให้อ่านและแก้ไขข้อบกพร่องของการทดสอบได้ง่ายขึ้น เนื่องจากจะระบุขั้นตอนและเงื่อนไขที่ล้มเหลวอย่างชัดเจน

ตัวอย่าง
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()

ลักษณะการทำงานของการทดสอบเชิงกำหนด

หลีกเลี่ยงการเพิ่มเงื่อนไขในการทดสอบที่แยกพฤติกรรมออกเป็นสาขา หากจำเป็นต้องแยกการยืนยัน ให้ใช้กรอบการทดสอบ 2 รายการที่แตกต่างกันแทน

ไม่ใช้ข้อยกเว้น

การทดสอบและตัวช่วยทั่วไปต้องใช้การยืนยันแทนข้อยกเว้น ซึ่งจะช่วยให้แก้ไขข้อบกพร่องได้ง่ายขึ้นและเป็นไปตามรูปแบบการทดสอบ

ตัวอย่าง
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)
ตัวอย่าง
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 เนื่องจากจะเพิ่มเวลาในการดำเนินการทดสอบและทำให้เกิดความไม่เสถียร

เมื่อการทดสอบต้องรอเหตุการณ์หรือการยืนยันเพื่อดำเนินการต่อ ให้ใช้เมธอดการรอที่ระบุไว้ในเฟรมเวิร์กแทน

ใช้วิธีการรออย่างระมัดระวังเนื่องจากการดำเนินการทดสอบจะยังคงถูกบล็อกจนกว่าเงื่อนไขจะตรงกันหรือหมดเวลา

เมื่อคุณต้องรอให้เงื่อนไขเสร็จสมบูรณ์ในการทดสอบ ให้ถามคำถามต่อไปนี้

  1. ระยะหมดเวลาที่เหมาะสมคือเท่าใด

    หากคาดว่าเหตุการณ์จะเกิดขึ้นภายในกรอบเวลาที่เฉพาะเจาะจง ระยะหมดเวลาควรตรงกับความคาดหวังนั้นเพื่อให้แน่ใจว่าการทดสอบจะล้มเหลวอย่างรวดเร็ว ลดระยะหมดเวลาหากจำเป็น (ค่าเริ่มต้นคือ 30 วินาที)

  2. การดำเนินการที่ทำโดยเมธอดการรอมีค่าใช้จ่ายสูงเพียงใด

    หลีกเลี่ยงการเรียกใช้การดำเนินการที่มีค่าใช้จ่ายสูงบ่อยๆ เพิ่มช่วงเวลาการสำรวจหากจำเป็น (ค่าเริ่มต้นคือ 0.5 วินาที)

กรอบการทดสอบที่มีข้อกำหนด

หากการทดสอบมีกรอบการทดสอบที่มีข้อกำหนดที่ชัดเจนในการทำงาน (เช่น เป้าหมายที่ควรเรียกใช้) คุณสามารถข้ามกรอบการทดสอบเหล่านั้นได้หากไม่ตรงกับข้อกำหนด ดังนี้

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