Robot Framework自定义测试库开发详解


Robot Framework是一个开源自动化测试框架,主要特点是基于关键字驱动,本文介绍自定义测试库及测试库文档创建方法。

目录
  • 简介
  • RF分层思想
  • 开发自定义测试库
    • 静态API
    • 动态API
    • 混合API
    • 导入Python脚本
  • 生成测试库文档

简介

Robot Framework是用于验收测试(Acceptance Testing),验收测试驱动开发(Acceptance Test Driven Development, ATDD)和
机器人流程自动化(Robotic Process Automation, RPA)的开源自动化框架,支持数据驱动、关键字驱动和行为驱动(BDD)。它具有简单的纯文本语法,并且可以使用Python或Java实现库的扩展。

Robot Framework核心框架使用Python实现,支持Python 2 和 Python 3,还可以在Jython(JVM),IronPython(.NET)和PyPy上运行。该框架具有丰富的生态,有各种通用库和工具,比如selenium测试库SeleniumLibrary,appium测试库AppiumLibrary等,更多测试库可查看官网http://robotframework.org 。

Robot Framework GitHub地址:https://github.com/robotframework/robotframework

RIDE地址:https://github.com/robotframework/RIDE

下图是基于Robot Framework进行web自动化(SeleniumLibrary)测试的架构图,

https://blog.codecentric.de/en/2012/04/robot-framework-tutorial-a-complete-example/

安装:

pip install robotframework
pip install robotframework-ride

RIDE是使用wxPython库编写的Robot Framework图形界面。

RF分层思想

一个好的设计模式有助于提高脚本开发速度,且利于维护。使用Robot Framework进行自动化用例开发时可以考虑使用分层的思想,类似于Page Object 设计模式。

顾名思义,“分层” 意思就是把一个过程分为多层,比如下面的例子:实现web UI自动化的登录测试。

一共分为了4层:

  • 01_测试用例:编写测试用例
  • 02_流程层: 封装操作流程关键字
  • 03_元素层:存放页面元素标签以及页面基本操作关键字,比如开关操作,点击,输入等
  • 04_数据:用来存放数据,比如全局变量

使用分层思想,将数据和流程分离开,能够减少一些由需求变更引发的修改,更易于扩展和维护。当然也可以使用其它分层方式,最终目的是让你的项目容易维护。

Robot Framework的测试库已经非常丰富了,如果还是无法满足需求,可以自己开发,下面来介绍如何开发自定义测试库。

开发自定义测试库

Robot Framework主要有三种API:静态API、动态API和混合API,下面分别进行介绍。

静态API

静态API直接将方法(methods)映射为关键字。关键字接收的参数和方法相同的参数一样。

1. 编写测试库

~\Lib\site-packages目录中新建一个包【比如MyLibrary】, 然后在创建的测试库MyLibrary目录下新建MyKeywords类文件(MyKeywords.py),用来封装要实现的关键字。

class MyKeywords():
    def test_add(self, a, b, c):
        """两数相加

        :a: value1

        :b: value2

        :c: 预期结果

        Example:
        | Test Add | 2 | 3 | 5 |

        """
        if float(a) + float(b) == float(c):
            return True
        else:
            raise RuntimeError("%s + %s != %s"%(a,b,c))

接下来在~\Lib\site-packages\MyLibrary 目录下新建version.py 文件,用于设置自定义测试库MyLibrary的版本信息。

# -*- coding:utf-8 -*-
VERSION = '0.0.1'

然后在__init__.py文件导入关键字及版本信息。

# -*- coding:utf-8 -*-
from .MyKeywords import MyKeywords
from .version import VERSION
_version_ = VERSION
class MyLibrary(MyKeywords):    
    """    MyLibrary    """    
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

2. 导入测试库

创建一个测试套件,然后导入测试库

查询测试库关键字信息(F5)

3. 编写用例脚本

*** Settings ***
Library           MyLibrary

*** Test Cases ***
case_001
    ${sum}    Test Add    2    3    5
    Should Be True    ${sum}

动态API

动态API和静态API的区别在于发现测试库关键字、关键字的参数、说明文档,以及关键字实际执行方式不同。Python版本中,静态API是使用反射的方式获取(python反射介绍可参考Python反射介绍),而动态API采用的是指定的方法来动态获取这些信息,主要包括4个方法:get_keyword_names、run_keyword、get_keyword_arguments和get_keyword_documentation 。这些方法其实也使用了Python反射函数来获取测试类属性信息。

使用动态API的好处就是可以更加灵活的编写测试库,动态API说明文档可参考:http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-library-api

1. 编写测试库

和静态API类似,在~\Lib\site-packages目录中新建一个包【MyDynamicLibrary】, 然后在创建的测试库MyDynamicLibrary目录下新建MyKeywords类文件(MyKeywords.py),用来封装要实现的关键字。

from robot.api.deco import keyword
from robot.api import logger

class MyKeywords(object):
    def __init__(self):
        self._cal = Calculate()

    def get_keyword_names(self):
        # 获取当前测试类的所有属性
        attributes = [(name, getattr(self._cal, name)) for name in dir(self._cal)]
        # 过滤没有设置robot_name的属性
        keywords = [(name, value) for name, value in attributes
                    if hasattr(value, 'robot_name')]
        # 返回关键字名称
        return [value.robot_name or name for name, value in keywords]

    def run_keyword(self, name, args, kwargs):
        print("Running keyword '%s' with positional arguments %s and named arguments %s."
              % (name, args, kwargs))
        func = getattr(self._cal, name)
        return func(*args,**kwargs)


class Calculate(object):
    
    def not_keyword(self):
        pass

    @keyword
    def test_sub(self, a, b, results=None):
        """两数相减
        """
        if float(a) - float(b) == float(results):
            return True
        else:
            raise RuntimeError("%s - %s != %s"%(a,b,results))

    @keyword
    def test_add(self, a, b, c):
        """两数相加

        :a: value1

        :b: value2

        :c: 预期结果

        Example:
        | Test Add | 2 | 3 | 5 |

        """
        if float(a) + float(b) == float(c):
            return True
        else:
            raise RuntimeError("%s + %s != %s"%(a,b,c))

get_keyword_names 方法实现动态获取关键字名称,此方法没有参数,并且必须返回包含当前测试库实现的关键字列表或数组。代码中使用到了python反射函数 getattr()hasattrdir()

run_keyword 用于执行关键字,包括2个或者3个参数:name, args, kwargs,name 为关键字方法名称,通过get_keyword_names 获得;args 为关键字方法的位置参数,kwargs为关键字参数,可以不设置。

get_keyword_namesrun_keyword 是动态库必须要有的方法,如果没有它们就会变成一个静态库。动态API涉及到的主要方法如下表。

方法 参数 说明
get_keyword_names 获取关键字名称
run_keyword name, arguments, kwargs 执行关键字
get_keyword_arguments name 获取关键字参数(可选方法)
get_keyword_types name 返回关键字参数类型信息(可选方法)
get_keyword_tags name 返回关键字标签(可选方法)
get_keyword_documentation name 返回关键字及测试库文档(可选方法)
get_keyword_source name 返回关键字源信息(可选方法)

Calculate是一个存放关键字的类,这个类可以写在其它模块中。

__init__.pyversion.py文件和静态API写法一样。

2. 导入测试库

导入测试库MyDynamicLibrary后,查询测试库关键字信息(F5)

从上图可以看到已经获取到了关键字,没有添加 @keyword 装饰器的not_keyword 关键字没有显示出来。

另外,Description那一列是空白的,Arguments没有显示关键字的真实参数,关键字注释文档也没有显示。这需要get_keyword_documentationget_keyword_arguments 这两个函数来实现文档和变量显示。将下面代码添加到MyKeywords类中:

def get_keyword_documentation(self, name):
    func = getattr(self._cal, name)
    return func.__doc__

def get_keyword_arguments(self, name):
    func = getattr(self._cal, name)
    func_args = func.__code__.co_varnames
    if func_args[0] == "self":
        func_args = func_args[1:]
        return func_args

重启Robot Framework,F5查看测试库关键字信息:

3. 编写用例脚本

*** Settings ***
Library           MyDynamicLibrary

*** Test Cases ***
case_001
    ${res}    Test Add    2    3    5
    Should Be True    ${res}

case_002
    ${res}    Test Sub    6    1    results=5
    Should Be True    ${res}

执行,日志如下:

Starting test: PO demo.01 测试用例.自定义测试库-动态API.case_001
20211119 18:00:00.980 :  INFO : Running keyword 'test_add' with positional arguments ('2', '3', '5') and named arguments {}.
20211119 18:00:00.980 :  INFO : ${res} = True
Ending test:   PO demo.01 测试用例.自定义测试库-动态API.case_001

Starting test: PO demo.01 测试用例.自定义测试库-动态API.case_002
20211119 18:00:00.982 :  INFO : Running keyword 'test_sub' with positional arguments ('6', '1') and named arguments {'results': '5'}.
20211119 18:00:00.982 :  INFO : ${res} = True
Ending test:   PO demo.01 测试用例.自定义测试库-动态API.case_002

动态库也可以借助robotlibcore 库来实现,编写动态API更加简洁。具体使用方法参考:https://github.com/robotframework/PythonLibCore

混合API

混合API就是静态API和动态API方式的混合使用。混合API通过使用 get_keyword_names 方法获取所有关键字,但是不使用 run_keyword 方法执行关键字,执行方式和静态API相同。

~\Lib\site-packages目录中新建一个包【MyHybridLibrary】, MyKeywords.py文件中编写关键字方法,使用 get_keyword_names 方法获取关键字,其它写法和静态API类似:

from robot.api.deco import keyword
from robot.api import logger

class MyKeywords(object):
    def get_keyword_names(self):
        # 获取当前测试类的所有属性
        attributes = [(name, getattr(self, name)) for name in dir(self)]
        # 过滤没有设置robot_name的属性
        keywords = [(name, value) for name, value in attributes
                    if hasattr(value, 'robot_name')]
        # 返回关键字名称
        return [value.robot_name or name for name, value in keywords]

    def not_keyword(self):
        pass

    @keyword
    def test_sub(self, a, b, results=None):
        """两数相减
        """
        if float(a) - float(b) == float(results):
            return True
        else:
            raise RuntimeError("%s - %s != %s"%(a,b,results))

    @keyword
    def test_add(self, a, b, c):
        """两数相加

        :a: value1

        :b: value2

        :c: 预期结果

        Example:
        | Test Add | 2 | 3 | 5 |

        """
        if float(a) + float(b) == float(c):
            return True
        else:
            raise RuntimeError("%s + %s != %s"%(a,b,c))

__init__.pyversion.py文件和静态API写法一样。

导入测试库MyHybridLibrary后,查询测试库关键字信息(F5)

导入Python脚本

如果不想创建包,可以直接写py文件,然后导入。

可以将测试库文件放在 ~\Lib\site-packages 目录下,这样在导入 Library 的时候可以直接写库名称就可以了,如果放在其它地方,需要使用绝对路径或者相对路径。

需要注意的是测试类名称要和文件名一致,比如编写测试库TestLlibrary.py:

class TestLlibrary():
    def __init__(self):
        pass

    def test_sub(self, a, b, results=None):
        """两数相减
        """
        if float(a) - float(b) == float(results):
            return True
        else:
            raise RuntimeError("%s - %s != %s"%(a,b,results))

这种方式和静态API的关键字读取、文档显示、关键字执行一样,其实它就是静态API。

生成测试库文档

可以将关键字方法的document注释(3个双引号括起来的内容)提取出来生成一个测试库文档。使用Libdoc工具可以很方便生成一份测试库文档,命令格式:

python -m robot.libdoc -f html MyLibrary MyLibrary.html

生成MyHybridLibrary库文档:

$ python -m robot.libdoc -f html MyHybridLibrary MyHybridLibrary.html
D:\MyHybridLibrary.html

MyHybridLibrary.html内容:

这非常方便,不需要另外编辑一份测试文档,更新关键字后,执行上面的命令就可以快速生成。

参考文档:

  1. https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html
  2. https://github.com/robotframework/PythonLibCore
--THE END--