变量文件
顾名思义, 变量文件中包含了测试数据中的 变量. 虽然变量可以通过变量表格中创建, 或者通过命令行设置, 不过这些方法有所局限, 而变量文件可以动态地创建任意类型的变量.
变量文件一般就是由Python模块实现, 有两种不同的方法来创建变量:
- 直接创建变量
- 变量就是模块的属性. 最简单的情形下, 这种语法几乎不需要真正的编程. 例如,
MY_VAR = 'my value'
就创建了变量${MY_VAR}
, 后面是变量的值.- 通过特殊函数获取变量
- 变量文件中可以包含一个特殊的函数
get_variables
(或者getVariables
), 该函数 将变量按字典的形式返回. 该函数还可以接受参数, 所以这种方法非常灵活.
此外变量文件还可以由 Python或Java类 <Implementing variable file as Python or Java class> 来实现. 具体的方法类似.
使用变量文件
通过Setting
所有的测试数据文件都可以在设置表中通过 Variables 来导入变量, 如同使用 Resource 来 导入资源文件 一样. 和资源文件的查找顺序类似, 待导入的变量文件路径最开始在相对于当前要导入变量的文件所在路径上寻找, 如果找不到, 则继续在 模块搜索路径 上搜寻. 路径名称可以使用变量, 并且在Windows中也可以使用正斜杠.
如果 变量文件可以接受参数, 这些参数跟在路径后面的单元格中, 并且这些参数同样可以使用变量.
- *** Settings ***
- Variables myvariables.py
- Variables ../data/variables.py
- Variables ${RESOURCES}/common.py
- Variables taking_arguments.py arg1 ${ARG2}
变量文件中定义的所有变量在导入它的测试文件中都是可见的. 如果同时导入了多个变量文件并且存在名称冲突, 则最先导入的生效. 此外, 通过变量表格和命令行方式设置的变量会覆盖变量文件中的同名变量.
通过命令行
还可以通过命令行选项 —variablefile
来指定变量文件. 选项后面跟着文件的路径, 如果要传递参数的话, 使用冒号 (:
) 来分隔:
- --variablefile myvariables.py
- --variablefile path/variables.py
- --variablefile /absolute/path/common.py
- --variablefile taking_arguments.py:arg1:arg2
从Robot Framework 2.8.2版本开始, 通过命令行设置的变量文件同样支持在 模块搜索路径 上搜寻.
如果文件路径使用了Windows的绝对路径格式, 驱动器号后面的冒号不会被视作分隔符:
- --variablefile C:\path\variables.py
从Robot Framework 2.8.7版本开始, 还可以使用分号(;
)作为参数的分隔符. 这种情况对参数本身也包含冒号时特别有用. 不过需要注意, 在UNIX-like操作系统中, 要使用双引号将整个选项值括起来:
- --variablefile "myvariables.py;argument:with:colons"
- --variablefile C:\path\variables.py;D:\data.xls
这些变量文件中的变量在所有测试文件中全局可见, 这点和通过选项 —variable
来设置 单个变量 类似.
如果同时使用了 —variablefile
和 —variable
选项, 并且发生变量名冲突, 则使用 —variable
选项设置的变量胜出.
直接创建变量
基础语法
当使用变量文件时, 它们像Python的模块一样被导入, 其中的非下划线(_
)开头的全局属性均被视作变量. 因为变量的名字是不区分大小写的, 所以不管小写还是大写字母都是可以的, 通常推荐大写字母用作全局变量和属性.
- VARIABLE = "An example string"
- ANOTHER_VARIABLE = "This is pretty easy!"
- INTEGER = 42
- STRINGS = ["one", "two", "kolme", "four"]
- NUMBERS = [1, INTEGER, 3.14]
- MAPPING = {"one": 1, "two": 2, "three": 3}
在上面的例子中, 创建了 ${VARIABLE}
, ${ANOTHER VARIABLE}
等变量. 前面2个是字符串, 第3个是整数, 接下来是两个列表, 最后一个是字典. 这些变量都可以用作 标量变量, 列表和字典还可以当作 列表变量 如 @{STRINGS}
(注字典当列表变量使用时只包含字典的键), 而字典显然可以被当作 字典变量 如 &{MAPPING}
.
如果想让列表和字典类型的变量显得更明确, 可以分别使用前缀 LIST
和 ``DICT``来区分(注意后面是两个下划线):
- from collections import OrderedDict
- LIST__ANIMALS = ["cat", "dog"]
- DICT__FINNISH = OrderedDict([("cat", "kissa"), ("dog", "koira")])
这些前缀最终不会被视作变量名称的一部分, 只是会让Robot Framework校验变量的值和类型是否符合. 对字典来说, 变量值还将转换为特殊的字典类型, 就像 创建字典变量 中使用的一样. 这样这些字典之中的值就可以像访问属性一样获取, 如 ${FINNISH.cat}
. 同时这些字典还是排序的, 不过如果想保持和原来的顺序一样则要求初始的字典是排序的.
上面例子中的变量同样可以使用下面的方式在变量表中创建.
- *** Variables ***
- ${VARIABLE} An example string
- ${ANOTHER VARIABLE} This is pretty easy!
- ${INTEGER} ${42}
- @{STRINGS} one two kolme four
- @{NUMBERS} ${1} ${INTEGER} ${3.14}
- &{MAPPING} one=${1} two=${2} three=${3}
- @{ANIMALS} cat dog
- &{FINNISH} cat=kissa dog=koira
注解
变量文件中的字符串中的变量格式是不会当变量替换的. 例如,VAR = "an ${example}"
将创建变量 ${VAR}
, 其值为 an ${example}
.是否存在变量 ${example}
都不会影响.
使用对象
变量文件中变量定义突破了变量表格中只能定义字符串和基础类型的限制, 现在变量可以包含任意类型的对象. 在下面的例子中, 变量 ${MAPPING}
包含了一个Java哈希表, 其中包含两个值(该例子只适用于Jython上运行).
- from java.util import Hashtable
- MAPPING = Hashtable()
- MAPPING.put("one", 1)
- MAPPING.put("two", 2)
第二个例子创建了Python的字典 ${MAPPING}
, 同样包含两个值, 且这两个值是该文件中自定义类的实例.
- MAPPING = {'one': 1, 'two': 2}
- class MyObject:
- def __init__(self, name):
- self.name = name
- OBJ1 = MyObject('John')
- OBJ2 = MyObject('Jane')
动态创建变量
因为变量文件就是真正的编程语言, 其中几乎可以包含任意的代码逻辑来设置变量.
- import os
- import random
- import time
- USER = os.getlogin() # current login name
- RANDOM_INT = random.randint(0, 10) # random integer in range [0,10]
- CURRENT_TIME = time.asctime() # timestamp like 'Thu Apr 6 12:45:21 2006'
- if time.localtime()[3] > 12:
- AFTERNOON = True
- else:
- AFTERNOON = False
上面的例子中使用了Python标准库来设置不同的变量, 你也可以使用自己的代码来构造这些值.
下面的例子展示了类似的概念, 真实的代码中的数据可以是来自数据库, 或者外部文件, 甚至是要求用户输入.
- import math
- def get_area(diameter):
- radius = diameter / 2
- area = math.pi * radius * radius
- return area
- AREA1 = get_area(1)
- AREA2 = get_area(2)
选择性的包含变量
当 Robot Framework 处理变量文件时, 这些文件(模块)中所有的属性只要不是以下划线开头, 都会被视作变量, 这其中甚至包括函数或类, 不管是在文件中创建的还是从其它模块导入的. 例如, 上面最后一个例子中除了 ${AREA1}
和 ${AREA2}
这两个我们预期的变量外, 最终还包含了 ${math}
和 ${get_area}
这两个变量.
虽然通常情况下这些额外的变量不会造成什么问题, 但是它们有可能会无意覆盖其它的变量名, 由此引发的错误将难以定位. 一个可行的解决办法是通过加下划线作为前缀来忽略这些属性:
- import math as _math
- def _get_area(diameter):
- radius = diameter / 2.0
- area = _math.pi * radius * radius
- return area
- AREA1 = _get_area(1)
- AREA2 = _get_area(2)
但是如果属性的数量非常多, 这样做就很不方便(同时, 这种做法也不符合Python的编码风格). 推荐的做法是使用特殊属性 all
, 将要作为变量暴露的属性名放在列表中赋值给它.
- import math
- __all__ = ['AREA1', 'AREA2']
- def get_area(diameter):
- radius = diameter / 2.0
- area = math.pi * radius * radius
- return area
- AREA1 = get_area(1)
- AREA2 = get_area(2)
注解
all
属性在Python中最初就是用来设置哪些属性可以在from modulename import *
的语法中被导入.
通过特殊函数获取变量
在变量文件中获取变量的另一种方法是通过特殊的函数 get_variables
). 如果这个函数存在, Robot Framework将调用该函数, 并且预期返回的结果是Python的字典类型或者Java中的 (或
getVariablesMap
类型, 其中变量的名称是键, 而值就是变量的值.
创建的变量可以用作标量, 列表和字典, 就和 直接创建变量 完全一样, 同样可以使用前缀 LIST
和 DICT
来明确表示创建的是列表和字典.
下面的例子和 直接创建变量 中的第一个例子在功能上完全相同.
- def get_variables():
- variables = {"VARIABLE ": "An example string",
- "ANOTHER VARIABLE": "This is pretty easy!",
- "INTEGER": 42,
- "STRINGS": ["one", "two", "kolme", "four"],
- "NUMBERS": [1, 42, 3.14],
- "MAPPING": {"one": 1, "two": 2, "three": 3}}
- return variables
get_variables
可以接受参数, 这样可以很方便的改变实际要创建什么样的变量. 参数的数量和类型和普通的Python函数并无二致. 当在测试数据中 使用变量文件 时, 调用参数跟在变量文件后面的表格里, 而在命令行中则通过冒号或分号和文件路径分开.
下面这个傻傻的例子展示了变量文件如何使用参数. 在更真实的场景中, 这些参数可能是一个用来读取参数的外部文件的路径, 或者是数据库的地址.
- variables1 = {'scalar': 'Scalar variable',
- 'LIST__list': ['List','variable']}
- variables2 = {'scalar' : 'Some other value',
- 'LIST__list': ['Some','other','value'],
- 'extra': 'variables1 does not have this at all'}
- def get_variables(arg):
- if arg == 'one':
- return variables1
- else:
- return variables2
用类实现变量文件
从Robot Framework 2.7版本开始, 还可以使用Python或Java之中的类来实现变量文件.
具体实现
因为变量导入时使用的文件路径, 所有使用类实现的时候有一些限制:
- Python的类名必须和所在的模块名相同.
- Java类必须在默认包中.
- 指向Java类的路径必须以
.java
或.class
结尾, class文件必须存在.
不管以何种语言实现, 框架都将不带参数的构造一个实例, 通过该实例获取变量. 和使用模块类似, 变量可以直接定义为实例的属性, 也可以使用特殊的 get_variables
) 方法.(或
getVariables
当直接定义变量时, 会忽略所有可调用的(callable)的属性以避免调用实例的方法. 如果需要可调用的变量, 需要使用其它的方法来创建变量文件.
示例
第一个例子通过属性直接创建变量, 同时以Python和Java两种语言实现. 两个例子的效果相同, 都通过类的属性创建了变量 ${VARIABLE}
and @{LIST}
, 并通过实例的属性创建变量 ${ANOTHER VARIABLE}
.
- class StaticPythonExample(object):
- variable = 'value'
- LIST__list = [1, 2, 3]
- _not_variable = 'starts with an underscore'
- def __init__(self):
- self.another_variable = 'another value'
- public class StaticJavaExample {
- public static String variable = "value";
- public static String[] LIST__list = {1, 2, 3};
- private String notVariable = "is private";
- public String anotherVariable;
- public StaticJavaExample() {
- anotherVariable = "another value";
- }
- }
第二个例子通过动态的方法来获取变量. 同样, 两种语言的效果一样, 都创建了唯一的变量 ${DYNAMIC VARIABLE}
.
- class DynamicPythonExample(object):
- def get_variables(self, *args):
- return {'dynamic variable': ' '.join(args)}
- import java.util.Map;
- import java.util.HashMap;
- public class DynamicJavaExample {
- public Map<String, String> getVariables(String arg1, String arg2) {
- HashMap<String, String> variables = new HashMap<String, String>();
- variables.put("dynamic variable", arg1 + " " + arg2);
- return variables;
- }
- }
YAML格式的变量文件
变量文件还可以使用 YAML 文件. YAML是一种数据序列化的标记语言, 拥有简单的语法和友好的可读性. 下面的例子展示了一个简单的YAML文件:
- string: Hello, world!
- integer: 42
- list:
- - one
- - two
- dict:
- one: yksi
- two: kaksi
- with spaces: kolme
注解
在Robot Framework中使用YAML文件要求安装 PyYAML 模块. 如果已经有了 pip_, 则使用下面的命令即可安装pip install pyyaml
.
Robot Framework从2.9版本开始支持YAML. 从2.9.2版本开始, 独立的JAR包安装 已经默认包含了PyYAML.
YAML 变量文件的使用和其它变量文件完全一样, 既可以使用命令行选项 —variablefile
, 也可以使用配置 Variables, 或者使用关键字 Import Variables 动态导入. 唯一需要记住的是, 导入YAML文件的路径名必须以 .yaml
扩展名结尾.
上例中的YAML文件创建的变量和下面的变量表格创建的变量完全一样.
- *** Variables ***
- ${STRING} Hello, world!
- ${INTEGER} ${42}
- @{LIST} one two
- &{DICT} one=yksi two=kaksi
使用YAML文件作为变量文件必须总是使用顶层的映射(mappings). 如上例所示, 映射中的键和值分别是变量的名称和值. 变量的值可以是YAML语法支持的任意数据类型. 如果名称或值中包含了non-ASCII的字符, 则YAML文件必须使用UTF-8编码格式.
如果值是mapping类型, 则最终将转换为特殊的字典, 这一点等同于在变量表格中 创建字典变量. 这样就可以使用 ${DICT.one}
这样的属性访问方法来获取到字典的值. 当然, 这里要求键的名字必须是合法的Python属性名称, 如果其中包含了空格或者其他非法的名称, 则还是可以使用 &{DICT}[with spaces]
语法来获取字典的值. 这个生成的字典也是有序的, 不过遗憾的是, 原始的YAML文件中的顺序没法保留下来.