RobotFramework类的初始化
上一篇讲到了run_cli
函数把命令行中的参数以列表的形式传给了RobotFramework
类中的execute_cli
函数。
先看下RobotFramework
类的__init__
初始化方法:
class RobotFramework(Application):
def __init__(self):
Application.__init__(self, USAGE, arg_limits=(1,),
env_options='ROBOT_OPTIONS', logger=LOGGER)
调用的是Application
类的__init__
初始化方法:
class Application(object):
def __init__(self, usage, name=None, version=None, arg_limits=None,
env_options=None, logger=None, **auto_options):
self._ap = ArgumentParser(usage, name, version, arg_limits,
self.validate, env_options, **auto_options)
self._logger = logger or DefaultLogger()
self._ap是一个ArgumentParser实例,可以推测是这里对传进来的参数做具体处理。
ArgumentParser
ArgumentParser字面意思就是参数解析器,来看看具体是怎么初始化的:
class ArgumentParser(object):
def __init__(self, usage, name=None, version=None, arg_limits=None,
validator=None, env_options=None, auto_help=True,
auto_version=True, auto_escape=True, auto_pythonpath=True,
auto_argumentfile=True):
"""Available options and tool name are read from the usage.
Tool name is got from the first row of the usage. It is either the
whole row or anything before first ' -- '.
"""
if not usage:
raise FrameworkError('Usage cannot be empty')
self.name = name or usage.splitlines()[0].split(' -- ')[0].strip()
self.version = version or get_full_version()
self._usage = usage
self._arg_limit_validator = ArgLimitValidator(arg_limits)
self._validator = validator
self._auto_help = auto_help
self._auto_version = auto_version
self._auto_escape = auto_escape
self._auto_pythonpath = auto_pythonpath
self._auto_argumentfile = auto_argumentfile
self._env_options = env_options
self._short_opts = ''
self._long_opts = []
self._multi_opts = []
self._flag_opts = []
self._short_to_long = {}
self._expected_args = ()
self._create_options(usage)
...
非常长的一大段,都是为这个类里的各种属性设置初始值,只需要看最后一行:
self._create_options(usage)
_create_options
这个函数是用来创建选项,选项是指我们前面说到的-L
, -d
, -t
这些。
选项的创建(create options)
选项的创建,就是把RF支持的选项构造出来,来看看是怎么具体实现的,也是在ArgumentParser类里:
class ArgumentParser(object):
...
def _create_options(self, usage):
for line in usage.splitlines():
res = self._opt_line_re.match(line)
if res:
self._create_option(short_opts=[o[1] for o in res.group(1).split()],
long_opt=res.group(3).lower(),
takes_arg=bool(res.group(4)),
is_multi=bool(res.group(5)))
_create_options
只接收里一个参数usage,拿变量usage里的每一行去做正则匹配:
- 用 splitlines() 按照行分隔符比如
\r, \n, \r\n
分隔,得到一个包含每一行作为元素的列表 - 用 re 模块里的 match() 函数,匹配含有选项用法的行
- 用 re 模块里的 group() 提取出该行匹配到的每一个分组内容,分别进行处理
匹配上的例如-L --loglevel level
这样的行,再用_create_option
函数根据匹配的分组内容去创建具体的选项,比如
- short_opts=['L'] (可以有多个短选项对应一个长选项)
- long_opt='loglevel'
- takes_arg=True
- is_multi=False
USAGE
usage具体的值是在RobotFramework
类里传入的常量USAGE
,它定义在run.py
里,非常长的一大段话,截取点主要内容:
USAGE = """Robot Framework -- A generic test automation framework
Version:
Usage: robot [options] data_sources
or: python -m robot [options] data_sources
or: python path/to/robot [options] data_sources
or: java -jar robotframework.jar [options] data_sources
Robot Framework is a Python-based keyword-driven test automation framework for
...
Options
=======
-F --extension value Parse only files with this extension when executing
a directory. Has no effect when running individual
files or when using resource files. If more than one
extension is needed, separate them with a colon.
Examples: `--extension robot`, `-F robot:txt`
New in RF 3.0.1.
-N --name name Set the name of the top level test suite. Underscores
in the name are converted to spaces. Default name is
created from the name of the executed data source.
...
re
re库是Python里非常常用的一个库,一般用法有:
- re.compile,编译正则表达式
- match,传入字符串进行匹配
- group/groups,提取匹配到每个分组里的字符串
RF这里用来匹配USAGE中选项用法的正则表达式如下:
class ArgumentParser(object):
_opt_line_re = re.compile('''
^\s{1,4} # 1-4 spaces in the beginning of the line
((-\S\s)*) # all possible short options incl. spaces (group 1)
--(\S{2,}) # required long option (group 3)
(\s\S+)? # optional value (group 4)
(\s\*)? # optional '*' telling option allowed multiple times (group 5)
''', re.VERBOSE)
re.VERBOSE可以把正则表达式写成多行,并且自动忽略空格。
这里一共五组规则,第一个规则是以一到四个空格开头,匹配到的返回匹配结果,匹配不到的返回None:
import re
space4_re = re.compile("^\s{1,4}")
space4_re.match("abc") # None
space4_re.match(" abc") # <_sre.SRE_Match object; span=(0, 3), match=' '>
space4_re.match(" abc") # <_sre.SRE_Match object; span=(0, 4), match=' '>
第二个规则是可能存在的短选项,如-t --test name *
中的-t
第三个规则是长选项,如-t --test name *
中的--test
第四个规则是可能存在的选项值
第五个规则是星号*
用法,带星号的选项允许出现若干次
下面例子来具体看下,每个group里都是什么
_opt_line_re.match(" -t --test name *").groups()
# ('-t ', '-t ', 'test', ' name', ' *')
_opt_line_re.match(" -h -? --help").groups()
# ('-h -? ', '-? ', 'help', None, None)
创建单个选项(create option)
class ArgumentParser(object):
...
def _create_option(self, short_opts, long_opt, takes_arg, is_multi):
self._verify_long_not_already_used(long_opt, not takes_arg)
for sopt in short_opts:
if sopt in self._short_to_long:
self._raise_option_multiple_times_in_usage('-' + sopt)
self._short_to_long[sopt] = long_opt
if is_multi:
self._multi_opts.append(long_opt)
if takes_arg:
long_opt += '='
short_opts = [sopt+':' for sopt in short_opts]
else:
if long_opt.startswith('no'):
long_opt = long_opt[2:]
self._long_opts.append('no' + long_opt)
self._flag_opts.append(long_opt)
self._long_opts.append(long_opt)
self._short_opts += (''.join(short_opts))
这里主要做了几件事:
- 保存短选项和长选项之间映射关系,存在字典
self._short_to_long
里,如{'F': 'extension', 'N': 'name', 'D': 'doc', 'M': 'metadata', 'G': 'settag', 't': 'test', 's': 'suite', 'i': 'include', 'e': 'exclude'}
- 如果是允许多次使用的选项,加到
self._multi_opts
,如['metadata', 'settag', 'test', 'suite', 'include', 'exclude']
- 如果是需要参数的选项,长选项后面加上'=',短选项后面加上':';如果是不需要参数的选项,加入到
self._flag_opts
里,加no
前缀再加入到self._long_opts
里 -
self._long_opts
里保存长选项列表,如['extension=', 'name=', 'doc=', 'metadata=', 'settag=', 'test=', 'suite=', 'include=', 'exclude=']
-
self._short_opts
里保存短选项字符串,如'F:N:D:M:G:t:s:i:e:R:S:c:n:v:V:d:o:l:r:x:b:TL:X.W:C:K:P:E:A:h?'
至此,得到了RF支持的所有选项和用法。
解析命令行参数
现在回到RobotFramework
类中的execute_cli
函数上
,看下RobotFramework
类的定义:
class RobotFramework(Application):
def __init__(self):
def main(self, datasources, **options):
def validate(self, options, arguments):
def _filter_options_without_value(self, options):
这里并没有重写execute_cli
方法,所以RobotFramework
类是继承了Application
类中的execute_cli
方法。
class Application(object):
...
def execute_cli(self, cli_arguments, exit=True):
with self._logger:
self._logger.info('%s %s' % (self._ap.name, self._ap.version))
options, arguments = self._parse_arguments(cli_arguments)
rc = self._execute(arguments, options)
if exit:
self._exit(rc)
return rc
顺藤摸瓜,依次调用:
- 调用了
Application
类的_parse_arguments
方法 - 再调用了
parse_arguments
方法(这是一个解析命令行参数的公共接口,接收一个列表作为参数) - 再调用了
ArgumentParser
类实例self._ap.parse_args
方法 - 再调用了
_parse_args
方法
所以直接来看下_parse_args
方法:
class ArgumentParser(object):
...
def _parse_args(self, args):
args = [self._lowercase_long_option(a) for a in args]
try:
opts, args = getopt.getopt(args, self._short_opts, self._long_opts)
except getopt.GetoptError as err:
raise DataError(err.msg)
return self._process_opts(opts), self._glob_args(args)
用的是getopt
库分别获取到opts和args
getopt
学习一下getopt
的使用,一个简单例子,在rf-reader/02/myops.py
文件:
import sys
import getopt
opts, args = getopt.getopt(sys.argv[1:], "L:d:t:", ["loglevel=", "test=", "outputdir="])
print(opts)
print(args)
在命令行执行:
python -m 02.myops -L DEBUG -d output --test testcasename testfilename
打印结果:
[('-L', 'DEBUG'), ('-d', 'output'), ('--test', 'testcasename')]
['testfilename']
- opts为已定义的选项及其值(定义的短选项和长选项分别通过getopt中的第二个和第三个参数传入)
- args为剩下的不属于已定义的选项格式信息的命令行参数,即短选项和长选项以外的参数。
现在再回头看下_parse_args
方法结尾出的两个方法:
-
_process_opts
,将[('-L', 'DEBUG'), ('-d', 'output'), ('--test', 'testcasename')]
这些元组处理成长选项的字典{'loglevel':'DEBUG', 'outputdir':'output', 'test':'testcasename'}
-
_glob_args
,返回的是文件的路径
最后,回顾下execute_cli
方法,它会将解析出来的参数opts和args会传入_execute
中去执行:
def execute_cli(self, cli_arguments, exit=True):
with self._logger:
self._logger.info('%s %s' % (self._ap.name, self._ap.version))
options, arguments = self._parse_arguments(cli_arguments)
rc = self._execute(arguments, options)
if exit:
self._exit(rc)
return rc
参数解析到此结束,后面就是真正的执行部分了。