Las pruebas del sistema se refieren a cualquier prueba de SDV creada con el framework de pruebas de SDV.
Creación de pruebas
Ubicación de la prueba
<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
Probar archivos
README.md: Todas las pruebas deben incluir una descripción del propósito de la prueba y cómo ejecutarla.Archivo de prueba: Todas las pruebas siguen una estructura similar:
"""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()
- Archivo de compilación:
Android.bp. La estructura del archivo es la siguiente:
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>",
}
Convención de nombres
Para identificar y encontrar los diferentes tipos de pruebas, estas deben crearse siguiendo una convención de nomenclatura específica.
Prueba de muestra
Nombre de archivo:
sdv_sample_<NAME>_test.pyNombre de la clase:
SdvSampleNameTest
Prueba de E2E
Nombre de archivo:
sdv_e2e_<NAME>_test.pyNombre de la clase:
SdvE2ENameTest
Prueba de larga duración
Nombre de archivo:
sdv_long_running_<NAME>_test.pyNombre de la clase:
SdvLongRunningNameTest
Prueba de rendimiento
Nombre de archivo:
sdv_performance_<NAME>_test.pyNombre de la clase:
SdvPerformanceNameTest
Prueba de hardware
Nombre de archivo:
sdv_hw_<NAME>_test.pyNombre de la clase:
SdvHWNameTest
Lineamientos de código
En esta sección, se proporcionan lineamientos y prácticas recomendadas para escribir pruebas del sistema de SDV.
Python y Mobly
Familiarízate con la guía de estilo de Python y las prácticas recomendadas de Mobly, y ten en cuenta las siguientes recomendaciones específicas para SDV:
Evita usar importaciones de Mobly directamente, excepto para las aserciones. El marco de trabajo de pruebas de SDV se basa en él y se enfoca en el SDV.
Afirmaciones: Usa afirmaciones de Mobly directamente.
Pruebas de SDV
En las siguientes secciones, se describen pautas y prácticas recomendadas específicas para desarrollar pruebas dentro del marco de trabajo de pruebas del SDV.
Configuración y limpieza
El código de configuración y limpieza debe estar fuera de los casos de prueba. Los métodos de desmontaje se llaman incluso si la prueba falla, para realizar una limpieza adecuada del dispositivo.
La ubicación del código de configuración y desmontaje depende de las necesidades específicas de la prueba, incluso si se interrumpe:
- Para ejecutar solo una vez al principio y al final de toda la prueba, usa
setup_classyteardown_class. Por ejemplo, obtener dispositivos, establecer valores de variables o estados que no cambian entre los casos de prueba, configurar propiedades comunes del dispositivo o establecer marcas.
def setup_class(self):
super().setup_class()
# setup code
def teardown_class(self):
# teardown code
super().teardown_class()
- Se ejecuta entre casos de prueba, antes y después de cada uno de ellos. Por ejemplo, una sesión interactiva o la ejecución de un servicio común.
def setup_test(self):
super().setup_test()
# setup code
def teardown_test(self):
# teardown code
super().teardown_test()
Casos de prueba con parámetros
Usa casos de prueba parametrizados cuando los pasos sean comunes en diferentes casos de prueba para evitar la repetición de código.
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
En el ejemplo, se crean dos casos de prueba, test_name_ab y test_name_cd.
Un caso de prueba para la verificación del comportamiento
Los casos de prueba deben ser compactos y enfocarse en un comportamiento específico. Si varios comportamientos comparten condiciones previas o pasos comunes, considera dividirlos. Puedes usar setup_test o la parametrización para minimizar la cantidad de código repetitivo.
Seguir este enfoque facilita la lectura y depuración de las pruebas, ya que indica claramente qué pasos y condiciones fallaron.
Ejemplo
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()
Comportamiento determinístico de las pruebas
Evita agregar condicionales en la prueba que ramifiquen su comportamiento. Si es necesario dividir una verificación, usa dos casos de prueba diferentes.
No uses excepciones
Las pruebas y los asistentes comunes deben usar aserciones en lugar de excepciones. Esto facilita la depuración y sigue patrones de prueba.
Ejemplo
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)
Ejemplo
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"
)
No usar la suspensión
Evita usar sleep, ya que aumenta el tiempo de ejecución de prueba y provoca inestabilidad.
Cuando la prueba requiera esperar un evento o una verificación para continuar, usa los métodos de espera que se proporcionan en el framework.
Usa los métodos de espera con cuidado, ya que la ejecución de la prueba permanece bloqueada hasta que se cumple la condición o se alcanza el tiempo de espera.
Cuando necesites esperar a que se complete una condición en una prueba, hazte las siguientes preguntas:
¿Qué es un tiempo de espera razonable?
Si se espera un evento dentro de un período específico, el tiempo de espera debe coincidir con esa expectativa para garantizar que la prueba falle rápidamente. Reduce el tiempo de espera si es necesario (el valor predeterminado es de 30 s).
¿Qué tan costosa es la operación que realiza el método de espera?
Evita llamar a operaciones costosas con frecuencia. Aumenta el intervalo de sondeo si es necesario (el valor predeterminado es 0.5 s).
Casos de prueba con requisitos
Si una prueba tiene casos de prueba con requisitos explícitos para funcionar (por ejemplo, el destino en el que se debe ejecutar), puedes omitirlos si no cumplen con los requisitos:
def test_with_requirement():
self.get_test_validator().skip_if(expr, reason)
# Test case