性质装饰器

本部分旨在解释 Kea 的性质定义装饰器是如何设计及实现的。

功能说明与功能设计

在KeaTest中,使用装饰器定义性质。装饰器的作用是对函数本身进行修改。在Kea中,用户的初始化、前置条件、主路径函数都是一个函数, 我们使用装饰器获取函数体,并对这个函数体进行标记。由于python中函数为一等对象,我们使用装饰器获取函数体后可以动态地往这个函数对象中 设置属性,我们根据不同的装饰器,设置不同的MARKER属性标记。在Kea加载性质的时候,我们读取如下的数据结构, 并将如下的数据结构通过KeaTestElements类进行读取,并转换为方便Kea读取和处理的数据结构:KeaTestElements。

../../../_images/decorators-keaTestElements.png

从用户自定义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