自动化测试 RobotFramework自定义静态测试类库总结

实践环境

win11 家庭中文版

Python 3.9.13

robotframework6.1.1

说明:为了方便的使用robot命令,安装好robotframwork后,修改系统环境,添加robot.exe(PYTHON_HOME/Scripts/robot.exe)所在路径到系统环境变量path

安装参考连接:https://github.com/robotframework/robotframework/blob/master/INSTALL.rst

docutils-0.21.2-py3-none-any.whl

pip install docutils==0.21.2

robotframework-ride 2.0.8.1

创建测试类库

创建测试库类或者模块

可通过Python模块或者类实现测试类库

类库名称

当某个库被导入时库时使用的测试库的名称与实现它的模块或类的名称相同。例如,如果您有一个Python模块MyLibrary(即文件MyLibrary.py),它将创建一个名为MyLibrary的库。

Python类总是在模块内部。如果实现库的类的名称与模块的名称相同,则Robot Framework允许在导入库时省略类名。例如,MyLib.py文件中的类MyLib可以用作名为MyLib的库。这也适用于子模块,例如,如果parent.MyLib模块具有类MyLib,也可以仅使用parent.MyLib导入。如果模块名称和类名不同,则必须同时使用模块和类名,如mymodule.MyLibrary 或者parent.submodule.MyLib

建议:

如果类库名称比较长,推荐给类库取个简单的别名。

为类库设置别名

如果多个关键字具有相同的名称,则必须关键字名称前面加上库名称作前缀。库名称通常来自实现它的模块或类名,但在某些情况下需要更改它:

  1. 需要使用不同的参数多次导入同一个库。
  2. 库名称太长,不方便使用。
  3. 希望使用变量在不同的环境中导入不同的库,但使用相同的名称引用它们。
  4. 该库的名称具有误导性,或者在其他方面很差。在这种情况下,更改实际名称当然是更好的解决方案。

指定新名称的基本语法是在库名称后面加上文本AS(区分大小写),然后再加上新名称。指定的名称显示在日志中,当使用关键字的全名(LibraryName.Keyword name)时,必须在测试数据中使用。

*** Settings ***
Library    packagename.TestLib    AS    TestLib
Library    ${LIBRARY}    AS    MyName

使用不同参数多次导入相同类库

*** Settings ***
Library    SomeLibrary    localhost        1234    AS    LocalLib
Library    SomeLibrary    server.domain    8080    AS    RemoteLib

*** Test Cases ***
Example
    LocalLib.Some Keyword     some arg       second arg
    RemoteLib.Some Keyword    another arg    whatever
    LocalLib.Another Keyword

为类库提供参数

所有作为类实现的测试类库,都可以接收参数。在Setting部分,类库名称后面指定这些参数,当Robot Framework创建导入库的实例时,会将这些参数传递给其构造函数。如果作为模块实现的库不能接受任何参数,因此尝试为类库指定参数会导致错误。

类库所需的参数数量与类库的构造函数所接受的参数数量相同。默认值和可变数量的参数与关键字参数的工作方式类似。传递给库的参数以及库名称本身都可以使用变量来指定,因此可以通过命令行进行更改。

*** Settings ***
Library    MyLibrary     10.0.0.1    8080
Library    AnotherLib    ${VAR}

上述使用的类库实现

from example import Connection

class MyLibrary:

    def __init__(self, host, port=80):
        self._conn = Connection(host, int(port))

    def send_message(self, message):
        self._conn.send(message)
        
class AnotherLib:

    def __init__(self, environment):
        self.environment = environment

    def do_something(self):
        if self.environment == 'test':
            # do something in test environment
        else:
            # do something in other environments

类库作用范围

作为类实现的库可以具有内部状态,该状态可以通过关键字和传递给库构造函数的参数进行更改。因为状态会影响关键字的实际行为,所以确保一个测试用例中的更改不会意外影响其他测试用例是很重要的。这种依赖关系可能会产生难以调试的问题,例如,当添加新的测试用例时,它们不一致地使用库。
Robot Framework试图保持测试用例彼此独立:默认情况下,它为每个测试用例创建新的测试库实例。然而,这种行为并不总是可取的,因为有时测试用例应该能够共享一个公共状态。此外,所有库都没有状态时,根本不需要创建库的新实例。
测试库可以控制何时使用类属性ROBOT_IBRARY_SCOPE创建库。此属性必须是字符串,并且可以具有以下三个值:

  • TEST

    为每个测试用例创建一个新实例。套件setup和套件teardown共享另一个实例。在Robot Framework 3.2之前,此值为TEST CASE,但现在建议使用TEST。因为所有未识别的值都被认为与TEST相同,所以这两个值都适用于所有版本。出于同样的原因,如果库的目标用于RPA,而非测试时,也可以使用值TASK。如果未设置ROBOT_LIBRARY_COPE属性时,默认为TEST

  • SUITE

    将为每个测试套件创建一个新实例。从测试用例文件创建并包含测试用例的最低级别的测试套件都有自己的实例,而更高级别的套件都有各自的实例用于可能的setupteardown。在Robot Framework 3.2之前,此值为TEST SUITE。该值仍然有效,但建议将SUITE用于面向Robot Framework 3.2及更新版本的库。

  • GLOBAL

    在整个测试执行过程中只创建一个实例,它由所有测试用例和测试套件共享。从模块创建的库始终是全局的

注意:

如果使用不同的参数多次导入同一个库,则每次都会创建一个新的实例,忽略ROBOT_LIBRARY_COPE配置。

SUITEGLOBAL作用域与具有状态的库一起使用时,建议库使用一些特殊关键字来清除状态。例如,可以在套件setupteardown中使用此关键字,以确保下一个测试套件中的测试用例可以从已知状态开始。例如,SeleniumLibrary使用GLOBAL作用域可以在不同的测试用例中使用同一个浏览器,而无需重新打开它,而且它还具有Close All Browsers关键字,可以轻松关闭所有打开的浏览器。

使用SUITE作用域的示例类库:

class ExampleLibrary:
    ROBOT_LIBRARY_SCOPE = 'SUITE'

    def __init__(self):
        self._counter = 0

    def count(self):
        self._counter += 1
        print(self._counter)

    def clear_counter(self):
        self._counter = 0

类库版本

当使用测试库时,Robot Framework会尝试确定其版本。然后将这些信息写入syslog,以提供调试信息。库文档工具Libdoc也将这些信息写入它生成的关键字文档中。

从属性ROBOT_IBRARY_VERSION读取版本信息,类似于从ROBOT_LIBARY_som读取库作用范围。如果ROBOT_IBRARY_VERSION不存在,则尝试从__version__属性读取信息。这些属性必须是类或模块属性,这取决于库是作为类还是模块实现的。

使用__version__的示例模块:

__version__ = '0.1'

def keyword():
    pass

创建关键字

什么方法被视为关键字

当使用静态库API时,Robot Framework使用反射来找出库类或模块实现了哪些关键字。默认情况下,它不包括以下划线开头的方法和函数所有未被忽略的方法和函数都被视为关键字。例如,下面的库实现了单个关键字My keyword

class MyLibrary:

    def my_keyword(self, arg):
        return self._helper_method(arg)

    def _helper_method(self, arg):
        return arg.upper()

限制public方法成为关键字

自动将所有公有方法和函数视为关键字通常效果良好,但有时候我们不期望这样。例如,当将库实现为类时,可能基类中的方法也被视为关键字,当将库实现为模块时, 当将库实现为模块时,导入到模块名称空间的函数也会自动成为关键字,这些可能不是我们想要的。

本节介绍如何防止方法和函数成为关键字。

基于类实现的类库

当库被实现为类时,可以通过将类属性ROBOT_AUTO_KEYWORDS 设置为False来告诉Robot Framework不要自动将方法暴露为关键字:

class Example:
    ROBOT_AUTO_KEYWORDS = False

ROBOT_AUTO_KEYWORDS属性被设置为False时,仅被使用@keyword 装饰器 显式修饰或以拥有robot_name属性的方法才会变成关键字。 @keyword 装饰器可以用于给关键字设置 自定义名称, 标签参数类型 )。

from robot.api.deco import keyword

class Example:
    ROBOT_AUTO_KEYWORDS = False
    
    @keyword
    def this_is_keyword(self):
        pass

    @keyword('This is keyword with custom name')
    def xxx(self):
        print('关键字: This is keyword with custom name')

    def this_is_not_keyword(self):
        pass

除了使用ROBOT_AUTO_KEYWORDS 属性,还可以使用更方便的 @library 装饰器来设置不要自动将方法暴露为关键字

from robot.api.deco import keyword, library


@library
class Example:

    @keyword
    def this_is_keyword(self):
        pass

    @keyword('This is keyword with custom name')
    def xxx(self):
        pass

    def this_is_not_keyword(self):
        pass

注意:

在ROBOT Framework 3.2中,使用ROBOT_AUTO_KEYWORDS 属性和 @library 装饰器来限制哪些方法成为关键字都是ROBOT Framework 3.2中新增的。
显式指定库实现的关键字的另一种方法是使用dynamic或者 hybrid 库api。

基于模块实现的类库

默认情况下,将库实现为模块时,模块命名空间中的所有函数都将成为关键字。对于导入的函数也是如此。例如,如果将以下模块用作类库,则它将包含关键字 Example Keyword,此外还包含关键字Current Thread

from threading import current_thread


def example_keyword():
    print('Running in thread "%s".' % current_thread().name)

避免导入的函数成为关键字的一个简单方法是只导入模块(例如import threading),并通过模块使用函数(例如threading.current_thread())或者,可以在导入时为函数提供一个以下划线开头的别名(例如from threading import current_thread as _current_thread

限制函数成为关键字的一种更明确的方法是使用模块级__all__属性,Python本身也将其用于类似的目的。如果使用它,则只有该属性列出的函数才可以成为关键字。例如,下面的库只实现了一个关键字Example Keyword

from threading import current_thread


__all__ = ['example_keyword']


def example_keyword():
    print('Running in thread "%s".' % current_thread().name)

def this_is_not_keyword():
    pass

如果类库很大,那么在添加、删除或重命名关键字时维护__all__属性可能是一项艰巨的任务。显式标记哪些函数是关键字的另一种方法是使用ROBOT_AUTO_KEYWORDS 属性。当该属性设置为False值时,只有用 [@keyword装饰器显式修饰的函数才会成为关键字。例如,此库也仅实现一个关键字Example Keyword

from threading import current_thread

from robot.api.deco import keyword


ROBOT_AUTO_KEYWORDS = False


@keyword
def example_keyword():
    print('Running in thread "%s".' % current_thread().name)

def this_is_not_keyword():
    pass

注意:

使用 ROBOT_AUTO_KEYWORDS 限制哪些函数成为关键字是ROBOT Framework 3.2中的一项新功能。

使用@not_keyword 装饰器

模块中的函数和类中的方法可以通过使用@not_keyword装饰器显式标记为非关键字。当一个库被实现为模块时,这个装饰器也可以用来避免导入的函数变成关键字。

from threading import current_thread

from robot.api.deco import not_keyword


not_keyword(current_thread)    # Don't expose `current_thread` as a keyword.


def example_keyword():
    print('Running in thread "%s".' % current_thread().name)

@not_keyword
def this_is_not_keyword():
    pass

与使用@library装饰器禁用自动关键字发现或将ROBOT_AUTO_KEYWORDS设置为False值相比,使用@not_keyword装饰器是避免函数或方法成为关键字的完全相反的方法。使用哪一个取决于上下文。

注意:

@not_keyword 是ROBOT Framework 3.2中新增功能。

关键字名称

Similarly both the do_nothing and doNothing methods can be used as the Do Nothing keyword in the test data.

将测试数据中使用的关键字名称与方法名称进行比较,以找到实现这些关键字的方法。名称比较不区分大小写,并且会忽略空格和下划线。例如,方法hello映射到关键字名称Hellohello甚至h e l l o。类似地,do_nothingdoNothing方法都可以用作测试数据中的Do Nothing关键字。

示例库在MyLibrary.py文件中实现为模块:

def hello(name):
    print("Hello, %s!" % name)

def do_nothing():
    pass

以下示例说明了如何使用上面的示例库。如果您想自己尝试,请确保库位于模块搜索路径.

*** Settings ***
Library    MyLibrary

*** Test Cases ***
My Test
    Do Nothing
    Hello    world
设置自定义名称

可以为关键字暴露一个不同的名称,而不是映射到方法名称的默认关键字名称。这可以通过将方法上的robot_name属性设置为所需的自定义名称来实现

def login(username, password):
    print('login')
    
login.robot_name = 'Login via user panel'
*** Test Cases ***
My Test
    Login Via User Panel    ${username}    ${password}

相比上述显示设置robot_name 属性,通常最简单的是使用 @keyword 装饰器

from robot.api.deco import keyword


@keyword('Login via user panel')
def login(username, password):
    # ...

使用不带参数的该装饰器不会对暴露的关键字名称产生影响,但仍会设置robot_name属性。这允许标记方法为关键字,但不会真的更改关键字名称。即使方法名称本身以下划线开头,具有robot_name属性的方法也会创建关键字。

设置自定义关键字名称还可以使库关键字使用嵌入式参数 语法接收参数。

关键字标签(tag)

类库关键字和用户关键字 可以拥有标签。可以通过在方法上设置 robot_tags 属性来定义类库关键字的标签:

from robot.api.deco import keyword


@keyword(tags=['tag1', 'tag2'])
def login(username, password):
    # ...

@keyword('Custom name', ['tags', 'here'])
def another_example():
    # ...

Another option for setting tags is giving them on the last line of keyword documentation with Tags: prefix and separated by a comma. For example:

设置tag的另一个方法是在关键字文档的最后一行添加标签:Tags:打头,以逗号分隔的标签名称。

例如

def login(username, password):
    """Log user in to SUT.

    Tags: tag1, tag2
    """
    # ...

关键字参数

使用静态和混合(hybrid)API,可以直接从实现关键字的方法获得关键字需要多少个参数的信息。使用 动态库API (https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-libraryapi)有其他方式来共享这些信息,因此本节与它们无关。

最常见也是最简单的情况是关键字需要确切数量的参数,在这种情况下,该方法只需接受这些参数。例如,一个实现不带参数的关键字方法也不接收参数,一个实现只带一个参数的关键字实现方法也接收一个参数,依此类推。

接收不同数量的参数的关键字示例

def no_arguments():
    print("Keyword got no arguments.")

def one_argument(arg):
    print("Keyword got one argument '%s'." % arg)

def three_arguments(a1, a2, a3):
    print("Keyword got three arguments '%s', '%s' and '%s'." % (a1, a2, a3))

关键字默认值

关键字使用的某些参数具有默认值,这通常很有用。

在Python中,一个方法总是只有一个实现,并且可能的默认值在方法定义中指定。下面展示了所有Python程序员都熟悉的语法

def one_default(arg='default'):
    print("Argument has value %s" % arg)

def multiple_defaults(arg1, arg2='default 1', arg3='default 2'):
    print("Got arguments %s, %s and %s" % (arg1, arg2, arg3))

上面的第一个示例关键字可以与零个或一个参数一起使用。如果没有给定任何参数,arg将取默认值default。如果有一个参数,arg将获得该值,并且使用多个参数调用关键字将失败。在第二个示例中,始终需要一个参数,但第二个和第三个参数具有默认值,因此可以将关键字与一到三个参数一起使用。

*** Test Cases ***
Defaults
    One Default
    One Default    argument
    Multiple Defaults    required arg
    Multiple Defaults    required arg    optional
    Multiple Defaults    required arg    optional 1    optional 2

可变数量的参数 (*varargs)

Robot Framework还支持接受任意数量参数的关键字。
Python支持接受任意数量参数的方法。相同的语法在库中也有效,如下面的示例所示,它也可以与其他指定参数的方式组合使用:

def any_arguments(*args):
    print("Got arguments:")
    for arg in args:
        print(arg)

def one_required(required, *others):
    print("Required: %s\nOthers:" % required)
    for arg in others:
        print(arg)

def also_defaults(req, def1="default 1", def2="default 2", *rest):
    print(req, def1, def2, rest)
*** Test Cases ***
Varargs
    Any Arguments
    Any Arguments    argument
    Any Arguments    arg 1    arg 2    arg 3    arg 4    arg 5
    One Required     required arg
    One Required     required arg    another arg    yet another
    Also Defaults    required
    Also Defaults    required    these two    have defaults
    Also Defaults    1    2    3    4    5    6

自由关键词参数(**kwargs)

Robot Framework支持Python的**kwargs语法。在本节中,我们将了解如何创建这样的关键字。
如以下示例显示了基本功能:

def example_keyword(**stuff):
    for name, value in stuff.items():
        print(name, value)
*** Test Cases ***
Keyword Arguments
    Example Keyword    hello=world        # Logs 'hello world'.
    Example Keyword    foo=1    bar=42    # Logs 'foo 1' and 'bar 42'.

通常,位于关键字最后的所有参数都使用命名语法规则name=value,并且与任何其他参数都不匹配的,将作为kwargs传递给关键字。为了避免使用像foo=quux这样的字面值作为自由关键字参数,必须对其进行转义比如foo\=quux

以下示例说明了普通位置参数、可变参数和关键词参数

def various_args(arg=None, *varargs, **kwargs):
    if arg is not None:
        print('arg:', arg)
    for value in varargs:
        print('vararg:', value)
    for name, value in sorted(kwargs.items()):
        print('kwarg:', name, value)
        
*** Test Cases ***
Positional
    Various Args    hello    world                # Logs 'arg: hello' and 'vararg: world'.

Named
    Various Args    arg=value                     # Logs 'arg: value'.

Kwargs
    Various Args    a=1    b=2    c=3             # Logs 'kwarg: a 1', 'kwarg: b 2' and 'kwarg: c 3'.
    Various Args    c=3    a=1    b=2             # Same as above. Order does not matter.

Positional and kwargs
    Various Args    1    2    kw=3                # Logs 'arg: 1', 'vararg: 2' and 'kwarg: kw 3'.

Named and kwargs
    Various Args    arg=value      hello=world    # Logs 'arg: value' and 'kwarg: hello world'.
    Various Args    hello=world    arg=value      # Same as above. Order does not matter.

仅限关键词参数

从Robot Framework 3.1开始,可以对关键字使用仅命名参数。Python的仅关键字参数提供了这种支持。 仅关键字参数在的*varargs之后指定,或者在不需要*varargs时在专用的*标记之后指定。可能的**kwargs是在仅关键字参数之后指定的。

例子:

def sort_words(*words, case_sensitive=False):
    key = str.lower if case_sensitive else None
    return sorted(words, key=key)

def strip_spaces(word, *, left=True, right=True):
    if left:
        word = word.lstrip()
    if right:
        word = word.rstrip()
    return word
    
*** Test Cases ***
Example
    Sort Words    Foo    bar    baZ
    Sort Words    Foo    bar    baZ    case_sensitive=True
    Strip Spaces    ${word}    left=False

仅限位置参数

Python支持所谓的仅限位置参数 ,这让指定指定某个参数智能作为位置参数提供,而非命名参数入 name=value。仅限位置参数在普通参数之前指定,并且在它们之后必须指定特殊的/标记

def keyword(posonly, /, normal):
    print(f"Got positional-only argument {posonly} and normal argument {normal}.")

可以这样使用上面的关键字:

*** Test Cases ***
Example
    # Positional-only and normal argument used as positional arguments.
    Keyword    foo    bar
    # Normal argument can also be named.
    Keyword    foo    normal=bar

如果仅限位置参数与包含等号的值(如example=usage)一起使用,则不被认为是命名参数语法,即使=之前的部分与参数名称匹配。此规则仅适用于仅限位置参数在其正确位置使用而没有其他参数在其前面使用名称参数语法的情况。

*** Test Cases ***
Example
    # Positional-only argument gets literal value `posonly=foo` in this case.
    Keyword    posonly=foo    normal=bar
    # This fails.
    Keyword    normal=bar    posonly=foo

从Robot Framework 4.0开始,完全支持仅限位置参数。将它们用作位置参数也适用于早期版本,但将它们用作命名参数会导致Python方面的错误。

......

更多详细信息请参考官方文档

参考连接

https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-test-libraries

https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#setting-custom-name-to-library