性质装饰器
本部分旨在解释 Kea 的性质定义装饰器是如何设计及实现的。
功能说明与功能设计
在KeaTest中,使用装饰器定义性质。装饰器的作用是对函数本身进行修改。在Kea中,用户的初始化、前置条件、主路径函数都是一个函数, 我们使用装饰器获取函数体,并对这个函数体进行标记。由于python中函数为一等对象,我们使用装饰器获取函数体后可以动态地往这个函数对象中 设置属性,我们根据不同的装饰器,设置不同的MARKER属性标记。在Kea加载性质的时候,我们读取如下的数据结构, 并将如下的数据结构通过KeaTestElements类进行读取,并转换为方便Kea读取和处理的数据结构:KeaTestElements。
从用户自定义KeaTest到运行时KeaTestElements的转换
性质的定义
下述的@rule和@precondition装饰器将用户定义的一条性质封装在数据结构Rule中,并对这个性质的函数进行使用RULE_MARKER进行标记。
以下是Rule数据数据结构的定义。precondition用于存放一个函数对象,存储一个计算前置条件的函数。function用于存储这条性质的交互场景(interaction scenario)。
@attr.s(frozen=True)
class Rule:
# `preconditions` denotes the preconditions annotated with `@precondition`
preconditions:Callable = attr.ib()
# `function` denotes the function of @Rule.
# This function includes the interaction scenario and the assertions (i.e., the postconditions)
function:Callable = attr.ib()
@rule装饰器用于定义一条性质。其中,RULE_MARKER为一个常量。
- 参数:
f: Callable[[Any], None]: 一个交互场景函数对象
- 返回:
Callable[[Any], None]: 被RULE_MARKER标记后已解析Rule的函数对象
def rule() -> Callable:
def accept(f):
precondition = getattr(f, PRECONDITIONS_MARKER, ())
rule = Rule(function=f, preconditions=precondition)
def rule_wrapper(*args, **kwargs):
return f(*args, **kwargs)
setattr(rule_wrapper, RULE_MARKER, rule)
return rule_wrapper
return accept
@precondition前提条件指定了性质何时可以被执行。一个性质可以有多个前提条件,每个前提条件由 @precondition 指定。其中, PRECONDITIONS_MARKER为一个常量。
- 参数:
precond: Callable[[Any], bool]: 一个返回布尔值的已经被@rule装饰过的函数对象
- 返回:
Callable[[Any], bool]: 被RULE_MARKER标记后已解析前置条件的函数
def precondition(precond: Callable[[Any], bool]) -> Callable:
def accept(f):
def precondition_wrapper(*args, **kwargs):
return f(*args, **kwargs)
rule:"Rule" = getattr(f, RULE_MARKER, None)
if rule is not None:
new_rule = rule.evolve(preconditions=rule.preconditions + (precond,))
setattr(precondition_wrapper, RULE_MARKER, new_rule)
else:
setattr(
precondition_wrapper,
PRECONDITIONS_MARKER,
getattr(f, PRECONDITIONS_MARKER, ()) + (precond,),
)
return precondition_wrapper
return accept
初始化函数的定义
@initializer定义一个初始化函数,用于应用的初始化,如跳过新手教程等。 下述的@initializer装饰器将用户定义的一条性质封装在数据结构Initializer中,并对这个性质的函数进行使用INITIALIZER_MARKER进行标记。
以下是Initializer数据结构的定义。function用于存放一个函数对象,为初始化时要执行的一系列操作。
@attr.s()
class Initializer:
# `function` denotes the function of `@initializer.
function:Callable = attr.ib()
@initializer装饰器用于定义一个初始化函数,其中,INITIALIZER_MARKER是一个常量。
- 参数:
f: Callable[[Any], None]: 定义了初始化事件的初始化函数对象
- 返回:
Callable[[Any], None]: 被INITIALIZER_MARKER标记的初始化函数对象
def initializer():
def accept(f):
def initialize_wrapper(*args, **kwargs):
return f(*args, **kwargs)
initializer_func = Initializer(function=f)
setattr(initialize_wrapper, INITIALIZER_MARKER, initializer_func)
return initialize_wrapper
return accept
主路径函数的定义
主路径指定了一系列事件,从应用起始页执行这些事件会将应用引到至性质的起始状态(满足前置条件的页面)。 下述的@mainPath装饰器将用户定义的一条性质封装在数据结构MainPath中,并对这个性质的函数进行使用MAINPATH_MARKER进行标记。
以下是MainPath数据结构的定义。function用于存放用户定义的mainPath函数对象,path为对这个函数进行源代码处理后获取的详细路径步骤, 为一个存储了主路径中各个步骤的源代码的列表。
@attr.s()
class MainPath:
# `function` denotes the function of `@mainPath.
function:Callable = attr.ib()
# the interaction steps (events) in the main path
path: List[str] = attr.ib()
@mainPath装饰器将用户定义的一条性质封装在数据结构MainPath中,其中,MAINPATH_MARKER是一个常量。
- 参数:
f: Callable[[Any], None]: 定义了主路径事件的函数对象
- 返回:
Callable[[Any], None]: 被MAINPATH_MARKER标记的初始化函数对象
def mainPath():
def accept(f):
def mainpath_wrapper(*args, **kwargs):
source_code = inspect.getsource(f)
code_lines = [line.strip() for line in source_code.splitlines() if line.strip()]
code_lines = [line for line in code_lines if not line.startswith('def ') and not line.startswith('@') and not line.startswith('#')]
return code_lines
main_path = MainPath(function=f, path=mainpath_wrapper())
setattr(mainpath_wrapper, MAINPATH_MARKER, main_path)
return mainpath_wrapper
return accept