Benutzerdefinierte Tools¶
Um ein benutzerdefiniertes Tool zu verwenden, muss dessen Python-Modul im Utilities-Verzeichnis
(wie im Menü angegeben)
liegen. Zudem muss eine Datei __init__.py im Verzeichnis des Python-Moduls existieren.
Anschließend kann das benutzerdefinierte Tool über die Testbenchkonfiguration konfiguriert und die im Tool definierten Tooljobs in den Testfall eingefügt werden. Der konkrete Aufbau eines benutzerdefinierten Tools ist in der Api-Dokumentation api/tools/usertool-api/usertool-api.html erläutert.
Migration von UserToolAdapter¶
Der veraltete UserToolAdapter wird nicht mehr unterstützt. Eine Anleitung zur Migration auf ein UserTool findet sich unter UserTool-Migrationshilfe.
Beispielimplementierungen¶
Verschiedene Beispielimplementierungen für benutzerdefinierte Tools sind im Unterverzeichnis
Templates/DefaultData/Utilities/ExampleUserTools/ des ecu.test Installationsverzeichnis
zu finden:
BasicExampleUserTool.pyGrundlegendes Beispiel eines UserTools einschließlich eines UserJobs zur Umsetzung dynamischer Parameterbereiche für Jobs.ExampleUserToolBasePort.pyBeispiel eines UserTools mit Base-Port durch Implementierung des UserPort Interfaces.ExampleUserToolImagePort.pyBeispiel eines UserTools mit Image-Port durch Implementierung der Interfaces UserImagePort und UserTouchInput.ExampleUserToolModelPort.pyBeispiel eines UserTools mit Modell-Port durch Implementierung der Interfaces UserModelPort, UserRecorder und UserSimulationManager. Durch die Implementierung eines Modell-Ports wird zudem der modellbasierte Bus-Port für das Tool verfügbar.
Das Minimalbeispiel in BasicExampleUserTool.py enthält den folgenden Code:
"""
This module contains an example of a user-defined Tool. The source code of this example
can be found in the ecu.test installation directory under ``Templates/DefaultData/Utilities``.
A user-defined Tool must be placed in the Utilities directory of the current workspace. If a Tool
is changed, it is necessary to stop the current test bench (if it uses the Tooladapter) and
select *Extras -> Update user libraries* in ecu.test.
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, TypedDict, NotRequired, Any, Literal
if TYPE_CHECKING:
from collections.abc import Callable
# Required so that ecu.test can discover this module and recognize it as a user tool.
MODULE_TYPE = 'USER_TOOL'
class ExampleUserTool:
def __init__(self, properties: dict[str, str | bool]) -> None:
"""
This method is called on tool start (test bench start, manual start in
configuration window). It can be used to start any external tool needed by this
ToolAdapter. Use `pass` if no external tool is used.
:param properties: Dictionary with the properties from the test bench configuration for
this tool and the configured values. The possible properties are
defined by the return value of the :func:`GetProperties` method.
"""
self._properties = properties
logging.getLogger('ecu.test').info(f'{self.GetToolName()} initialized')
@classmethod
def GetToolName(cls) -> str:
"""
The name of the tool. This name is displayed in the test bench configuration.
"""
return 'ExampleUserTool'
@classmethod
def GetJobs(cls) -> list[Callable]:
"""
Returns a list of methods that should be registered on this tool as jobs.
"""
return [cls.ExampleJob, DynamicJob]
@classmethod
def GetProperties(cls) -> dict[str, dict]:
"""
Properties of the tool. These properties can be configured in the test bench configuration.
The configured properties are passed to the `__init__` method when the configuration is
started. Available property types are STRING, TEXT, BOOL and CHOICE. Properties may be
sorted into groups to improve visual separation in the test bench configuration.
Informational properties can be marked readonly.
For BOOL and CHOICE properties, it is possible to define sub properties that are only
available for a given property value.
"""
return {
'Option 1': {
'Description': 'This is Option 1',
'Type': 'STRING',
'Default': 'default value',
},
'Group': {
'Option 2': {
'Description': 'This is Option 2',
'Type': 'BOOL',
'Default': True,
},
'Option 3': {
'Description': 'This is Option 3',
'Type': 'CHOICE',
'Choices': ['c1', 'c2', 'c3'],
'Default': 'c2',
},
'Nested group': {
'Option 4': {
'Description': 'This is Option 4',
'Type': 'STRING',
'Default': '4',
},
},
},
'Group with sub properties': {
'Option 5': {
'Description': 'This BOOL option has a sub property',
'Type': 'BOOL',
'Default': True,
'Children': {
'Sub option 5.1': {
'Description': 'This option is visible if Option 5 is checked',
'Type': 'TEXT',
'ReadOnly': True,
'Default': 'Information on\nmultiple lines.\n',
},
},
},
'Option 6': {
'Description': 'This CHOICE option has a sub property',
'Type': 'CHOICE',
'Choices': {
'c1': {
'Sub option 6.1': {
'Description': 'This option is only visible for choice c1',
'Type': 'STRING',
'Default': 'default value',
},
},
'c2': {
},
},
'Default': 'c2',
},
},
}
def GetProperty(self, name: str) -> str | bool | None:
return self._properties.get(name)
def GetVersion(self) -> str:
"""
Version of the external tool. This version will be documented in the test report.
"""
return '1.0.0'
def ExampleJob(self, paramOne: float = 1.0, paramTwo: float = 2.0) -> float:
"""
Example job implementation. The function arguments will be automatically converted to job
parameters based on their type annotations. This docstring will be used as the job's
description. See `GetJobs()` for how to register this method as a job.
:param paramOne: Description of paramOne
:param paramTwo: Description of paramTwo
:return: Multiplication of paramOne, paramTwo and value from TBC
"""
try:
valueFromTBC = float(self._properties['Option 4'])
except ValueError:
valueFromTBC = 1.0
return paramOne * paramTwo * valueFromTBC
class DynamicParameterInformation(TypedDict):
Choices: NotRequired[list[Any]]
Default: NotRequired[Any]
class DynamicJob:
"""
Example implementation of a job that allows the choice options and default values of selected
parameters to be determined dynamically.
"""
def __init__(self, parent: ExampleUserTool) -> None:
self._parent = parent
@classmethod
def GetCategory(cls) -> str:
"""
Optional method to specify the category of the job. Categories are used to group jobs in the
job panel configuration.
:return: The category of the job as a string.
"""
return 'Example Category'
def GetDynamicParameterInformation(self) -> dict[str, DynamicParameterInformation]:
"""
This method specifies the choice options for selected parameters.
:return: Dictionary where the key represents the parameter name and the value contains the
dynamically determined choices and default values.
"""
if self._parent.GetProperty('Option 2'):
choices = ['a', 'b', 'c']
else:
choices = ['x', 'y', 'z']
return {
'param2': {
'Choices': choices,
'Default': choices[0]
},
}
def Call(self, param1: Literal['on', 'off'], param2: str) -> str:
"""
This method is executed when the job is executed in ecu.test.
:param param1: Description of param1
:param param2: Description of param2
:return: Output string containing param1 and param2.
"""
return f'The parameters were set to {param1} and {param2}.'