System tests refer to any SDV test created using the SDV Test Framework.
Test creation
Test location
<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 files
README.md: All tests must include a description of the test's purpose and how to run the test.Test file: All tests follow a similar structure:
"""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 file:
Android.bp. The file structure is as follows:
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>",
}
Naming Convention
To identify and find the different types of tests, tests must be created following a specific naming convention.
Sample Test
Filename:
sdv_sample_<NAME>_test.pyClass name:
SdvSampleNameTest
E2E Test
Filename:
sdv_e2e_<NAME>_test.pyClass name:
SdvE2ENameTest
Long-Running Test
Filename:
sdv_long_running_<NAME>_test.pyClass name:
SdvLongRunningNameTest
Performance Test
Filename:
sdv_performance_<NAME>_test.pyClass name:
SdvPerformanceNameTest
Hardware Test
Filename:
sdv_hw_<NAME>_test.pyClass name:
SdvHWNameTest
Code Guidelines
This section provides guidelines and best practices for writing SDV System Tests.
Python and Mobly
Familiarize yourself with Python style guide and Mobly best practices, and consider the following SDV-specific recommendations:
Avoid using Mobly imports directly except for assertions. The SDV Test Framework builds on it with a focus on SDV.
Assertions: Use Mobly assertions directly.
SDV Tests
The following sections outline specific guidelines and best practices for developing tests within the SDV Test Framework.
Setup and Cleanup
Setup and cleanup code must be outside the test cases. Teardown methods are called even if the test fails, to make proper device cleanup.
The location for setup and teardown code depends on the test's specific needs, even if the test is interrupted:
- To run only once at the beginning and end of the entire test, use
setup_classandteardown_class. For example, get devices, set variable values or state that does not change between test cases, configure common device properties, or set flags.
def setup_class(self):
super().setup_class()
# setup code
def teardown_class(self):
# teardown code
super().teardown_class()
- To run between test cases, before and after each of them. For example, an interactive session or common service execution.
def setup_test(self):
super().setup_test()
# setup code
def teardown_test(self):
# teardown code
super().teardown_test()
Parameterized test cases
Use parameterized test cases when the steps are common across different test cases to avoid code repetition.
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
The example creates two test cases test_name_ab and test_name_cd.
One test case for behavior verification
Test cases should be compact and focus on one specific behavior. If multiple
behaviors share common preconditions or steps, consider splitting them. You can
use setup_test or parameterization to minimize the amount of repetitive code.
Following this approach makes tests easier to read and debug because it clearly indicates which steps and conditions failed.
Example
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()
Deterministic test behavior
Avoid adding conditionals in the test that branch its behavior. If a verification needs to be split, use two different test cases instead.
Don't use Exceptions
Tests and common helpers must use assertions instead of exceptions. This facilitates debugging and follows testing patterns.
Example
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)
Example
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"
)
Don't use sleep()
Avoid using sleep() because it increases test execution time and causes
flakiness.
When the test requires waiting for an event or verification to continue, use the Waiting Methods provided in the framework instead.
Use waiting methods carefully because test execution remains blocked until the condition matches or the timeout is reached.
When you need to wait for a condition to complete in a test, ask the following questions:
What is a reasonable timeout?
If an event is expected within a specific timeframe, the timeout should match that expectation to make sure the test fails quickly. Reduce the timeout if necessary (the default is 30s).
How expensive is the operation performed by the waiting method?
Avoid calling expensive operations frequently. Increase the poll interval if necessary (the default is 0.5s).
Test cases with requirements
If a test has test cases with explicit requirements to work (for example, the target where it should run), you can skip them if they don't match the requirements:
def test_with_requirement():
self.get_test_validator().skip_if(expr, reason)
# Test case