Robot Framework 源代码阅读笔记 之 一
来源:互联网 发布:无锡关键词排名优化 编辑:程序博客网 时间:2024/05/21 09:56
从源代码里应该可以帮我解答一些问题:
1. 这些关键字怎么定义的,然后怎么对应到具体的库来执行的
2. 框架的逻辑组织架构是什么样的,能学到哪些东西
3. 有没有一些可以改进的地方
4. 用到了哪些语言特性和技巧
5. 其他一些思考
安装完之后的入口robot文件如下:
https://github.com/robotframework/robotframework/tree/master/src/bin
从Robot开始,Linux应该运行的文件是下面这个
robotframework/src/bin/robot
内容如下:
#!/usr/bin/env python
import sys
from robot import run_cli
# Multiprocessing guard
# https://github.com/robotframework/robotframework/issues/2315
if __name__ == '__main__':
run_cli(sys.argv[1:])
可以看到模块robot的run_cli才是真正的入口
而从robot模块可以看到:
https://github.com/robotframework/robotframework/blob/master/src/robot/__init__.py
from robot.rebot import rebot, rebot_cli
from robot.run import run, run_cli
from robot.version import get_version
__all__ = ['run', 'run_cli', 'rebot', 'rebot_cli']
__version__ = get_version()
其实run_cli是从robot.run模块导出的
找到模块文件:https://github.com/robotframework/robotframework/blob/master/src/robot/run.py
def run_cli(arguments, exit=True):
"""Command line execution entry point for running tests.
:param arguments: Command line options and arguments as a list of strings.
:param exit: If ``True``, call ``sys.exit`` with the return code denoting
execution status, otherwise just return the rc. New in RF 3.0.1.
Entry point used when running tests from the command line, but can also
be used by custom scripts that execute tests. Especially useful if the
script itself needs to accept same arguments as accepted by Robot Framework,
because the script can just pass them forward directly along with the
possible default values it sets itself.
Example::
from robot import run_cli
# Run tests and return the return code.
rc = run_cli(['--name', 'Example', 'tests.robot'], exit=False)
# Run tests and exit to the system automatically.
run_cli(['--name', 'Example', 'tests.robot'])
See also the :func:`run` function that allows setting options as keyword
arguments like ``name="Example"`` and generally has a richer API for
programmatic test execution.
"""
return RobotFramework().execute_cli(arguments, exit=exit)
run_cli是命令行执行的入口,对应到RobotFramework().execute_cli才是真正模块代码的入口
要知道RobotFramework().execute_cli是从哪里进入的,还要看看import了哪些模块
from robot.conf import RobotSettings
from robot.model import ModelModifier
from robot.output import LOGGER, pyloggingconf
from robot.reporting import ResultWriter
from robot.running import TestSuiteBuilder
from robot.utils import Application, unic
class RobotFramework(Application):
def __init__(self):
Application.__init__(self, USAGE, arg_limits=(1,),
env_options='ROBOT_OPTIONS', logger=LOGGER)
RobotFramework继承了Application,下面看看application到底有什么
https://github.com/robotframework/robotframework/blob/master/src/robot/utils/application.py
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()
def main(self, arguments, **options):
raise NotImplementedError
def validate(self, options, arguments):
return options, arguments
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
def _execute(self, arguments, options):
try:
rc = self.main(arguments, **options)
except DataError as err:
return self._report_error(err.message, help=True)
except (KeyboardInterrupt, SystemExit):
return self._report_error('Execution stopped by user.',
rc=STOPPED_BY_USER)
except:
error, details = get_error_details(exclude_robot_traces=False)
return self._report_error('Unexpected error: %s' % error,
details, rc=FRAMEWORK_ERROR)
else:
return rc or 0
这个application基类的作用也就是parse了一下参数,真正的执行还是要回到RobotFramework里面定义的main
https://github.com/robotframework/robotframework/blob/master/src/robot/run.py
class RobotFramework(Application):
def __init__(self):
Application.__init__(self, USAGE, arg_limits=(1,),
env_options='ROBOT_OPTIONS', logger=LOGGER)
def main(self, datasources, **options):
settings = RobotSettings(options)
LOGGER.register_console_logger(**settings.console_output_config)
LOGGER.info('Settings:\n%s' % unic(settings))
suite = TestSuiteBuilder(settings['SuiteNames'],
settings['WarnOnSkipped'],
settings['Extension']).build(*datasources)
suite.configure(**settings.suite_config)
if settings.pre_run_modifiers:
suite.visit(ModelModifier(settings.pre_run_modifiers,
settings.run_empty_suite, LOGGER))
with pyloggingconf.robot_handler_enabled(settings.log_level):
result = suite.run(settings)
LOGGER.info("Tests execution ended. Statistics:\n%s"
% result.suite.stat_message)
if settings.log or settings.report or settings.xunit:
writer = ResultWriter(settings.output if settings.log
else result)
writer.write_results(settings.get_rebot_settings())
return result.return_code
看起来需要关注的就是TestSuiteBuilder,然后怎么run测试的,找找代码在哪里
https://github.com/robotframework/robotframework/blob/master/src/robot/running/__init__.py
from .builder import TestSuiteBuilder, ResourceFileBuilder
from .context import EXECUTION_CONTEXTS
from .model import Keyword, TestCase, TestSuite
from .testlibraries import TestLibrary
from .usererrorhandler import UserErrorHandler
from .userkeyword import UserLibrary
from .runkwregister import RUN_KW_REGISTER
https://github.com/robotframework/robotframework/blob/master/src/robot/running/builder.py
class TestSuiteBuilder(object):
"""Creates executable :class:`~robot.running.model.TestSuite` objects.
Suites are build based on existing test data on the file system.
See the overall documentation of the :mod:`robot.running` package for
more information and examples.
"""
def __init__(self, include_suites=None, warn_on_skipped=False, extension=None):
"""
:param include_suites: List of suite names to include. If ``None`` or
an empty list, all suites are included. When executing tests
normally, these names are specified using the ``--suite`` option.
:param warn_on_skipped: Boolean to control should a warning be emitted
if a file is skipped because it cannot be parsed or should it be
ignored silently. When executing tests normally, this value is set
with the ``--warnonskippedfiles`` option.
:param extension: Limit parsing test data to only these files. Files
are specified as an extension that is handled case-insensitively.
Same as ``--extension`` on the command line.
"""
self.include_suites = include_suites
self.warn_on_skipped = warn_on_skipped
self.extensions = self._get_extensions(extension)
builder = StepBuilder()
self._build_steps = builder.build_steps
self._build_step = builder.build_step
def build(self, *paths):
"""
:param paths: Paths to test data files or directories.
:return: :class:`~robot.running.model.TestSuite` instance.
"""
if not paths:
raise DataError('One or more source paths required.')
if len(paths) == 1:
return self._parse_and_build(paths[0])
root = TestSuite()
for path in paths:
root.suites.append(self._parse_and_build(path))
return root
看起来主要目的是创建TestSuite,再看看是什么东西
https://github.com/robotframework/robotframework/blob/master/src/robot/running/model.py
就列出来看起来比较关键的部分:
from robot importmodel
class TestSuite(model.TestSuite):
"""Represents a single executable test suite.
See the base class for documentation of attributes not documented here.
"""
__slots__ = ['resource']
test_class = TestCase #: Internal usage only.
keyword_class = Keyword #: Internal usage only.
def __init__(self, name='', doc='', metadata=None, source=None):
model.TestSuite.__init__(self, name, doc, metadata, source)
#: :class:`ResourceFile` instance containing imports, variables and
#: keywords the suite owns. When data is parsed from the file system,
#: this data comes from the same test case file that creates the suite.
self.resource = ResourceFile(source=source)
def configure(self, randomize_suites=False, randomize_tests=False,
randomize_seed=None, **options):
"""A shortcut to configure a suite using one method call.
:param randomize_xxx: Passed to :meth:`randomize`.
:param options: Passed to
:class:`~robot.model.configurer.SuiteConfigurer` that will then
set suite attributes, call :meth:`filter`, etc. as needed.
Example::
suite.configure(included_tags=['smoke'],
doc='Smoke test results.')
"""
model.TestSuite.configure(self, **options)
self.randomize(randomize_suites, randomize_tests, randomize_seed)
def run(self, settings=None, **options):
from .namespace import IMPORTER
from .signalhandler import STOP_SIGNAL_MONITOR
from .runner import Runner
with LOGGER:
if not settings:
settings = RobotSettings(options)
LOGGER.register_console_logger(**settings.console_output_config)
with pyloggingconf.robot_handler_enabled(settings.log_level):
with STOP_SIGNAL_MONITOR:
IMPORTER.reset()
output = Output(settings)
runner = Runner(output, settings)
self.visit(runner)
output.close(runner.result)
return runner.result
再看看这个testsuitemodel是怎么回事
https://github.com/robotframework/robotframework/blob/master/src/robot/model/testsuite.py
class TestSuite(ModelObject):
"""Base model for single suite.
Extended by :class:`robot.running.model.TestSuite` and
:class:`robot.result.model.TestSuite`.
"""
__slots__ = ['parent', 'source', '_name', 'doc', '_my_visitors']
test_class = TestCase #: Internal usage only.
keyword_class = Keyword #: Internal usage only.
def __init__(self, name='', doc='', metadata=None, source=None):
self.parent = None #: Parent suite. ``None`` with the root suite.
self._name = name
self.doc = doc #: Test suite documentation.
self.metadata = metadata
self.source = source #: Path to the source file or directory.
self.suites = None
self.tests = None
self.keywords = None
self._my_visitors = []
def visit(self, visitor):
""":mod:`Visitor interface <robot.model.visitor>` entry-point."""
visitor.visit_suite(self)
看起来,还是要看看这个Runner怎么visit_suite
https://github.com/robotframework/robotframework/blob/master/src/robot/running/runner.py
from robot.model import SuiteVisitor
class Runner(SuiteVisitor):
def __init__(self, output, settings):
self.result = None
self._output = output
self._settings = settings
self._variables = VariableScopes(settings)
self._suite = None
self._suite_status = None
self._executed_tests = None
@property
def _context(self):
return EXECUTION_CONTEXTS.current
def start_suite(self, suite):
self._output.library_listeners.new_suite_scope()
result = TestSuite(source=suite.source,
name=suite.name,
doc=suite.doc,
metadata=suite.metadata,
starttime=get_timestamp())
if not self.result:
result.set_criticality(self._settings.critical_tags,
self._settings.non_critical_tags)
self.result = Result(root_suite=result)
self.result.configure(status_rc=self._settings.status_rc,
stat_config=self._settings.statistics_config)
else:
self._suite.suites.append(result)
self._suite = result
self._suite_status = SuiteStatus(self._suite_status,
self._settings.exit_on_failure,
self._settings.exit_on_error,
self._settings.skip_teardown_on_exit)
ns = Namespace(self._variables, result, suite.resource)
ns.start_suite()
ns.variables.set_from_variable_table(suite.resource.variables)
EXECUTION_CONTEXTS.start_suite(result, ns, self._output,
self._settings.dry_run)
self._context.set_suite_variables(result)
if not self._suite_status.failures:
ns.handle_imports()
ns.variables.resolve_delayed()
result.doc = self._resolve_setting(result.doc)
result.metadata = [(self._resolve_setting(n), self._resolve_setting(v))
for n, v in result.metadata.items()]
self._context.set_suite_variables(result)
self._output.start_suite(ModelCombiner(suite, result,
tests=suite.tests,
suites=suite.suites,
test_count=suite.test_count))
self._output.register_error_listener(self._suite_status.error_occurred)
self._run_setup(suite.keywords.setup, self._suite_status)
self._executed_tests = NormalizedDict(ignore='_')
def _resolve_setting(self, value):
if is_list_like(value):
return self._variables.replace_list(value, ignore_errors=True)
return self._variables.replace_string(value, ignore_errors=True)
def end_suite(self, suite):
self._suite.message = self._suite_status.message
self._context.report_suite_status(self._suite.status,
self._suite.full_message)
with self._context.suite_teardown():
failure = self._run_teardown(suite.keywords.teardown, self._suite_status)
if failure:
self._suite.suite_teardown_failed(unic(failure))
if self._suite.statistics.critical.failed:
self._suite_status.critical_failure_occurred()
self._suite.endtime = get_timestamp()
self._suite.message = self._suite_status.message
self._context.end_suite(ModelCombiner(suite, self._suite))
self._suite = self._suite.parent
self._suite_status = self._suite_status.parent
self._output.library_listeners.discard_suite_scope()
if not suite.parent:
IMPORTER.close_global_library_listeners()
def visit_test(self, test):
if test.name in self._executed_tests:
self._output.warn("Multiple test cases with name '%s' executed in "
"test suite '%s'." % (test.name, self._suite.longname))
self._executed_tests[test.name] = True
result = self._suite.tests.create(name=test.name,
doc=self._resolve_setting(test.doc),
tags=self._resolve_setting(test.tags),
starttime=get_timestamp(),
timeout=self._get_timeout(test))
self._context.start_test(result)
self._output.start_test(ModelCombiner(test, result))
status = TestStatus(self._suite_status, result.critical)
if not status.failures and not test.name:
status.test_failed('Test case name cannot be empty.')
if not status.failures and not test.keywords.normal:
status.test_failed('Test case contains no keywords.')
if status.exit:
self._add_exit_combine()
result.tags.add('robot-exit')
self._run_setup(test.keywords.setup, status, result)
try:
if not status.failures:
StepRunner(self._context,
test.template).run_steps(test.keywords.normal)
else:
status.test_failed(status.message)
except PassExecution as exception:
err = exception.earlier_failures
if err:
status.test_failed(err)
else:
result.message = exception.message
except ExecutionFailed as err:
status.test_failed(err)
result.status = status.status
result.message = status.message or result.message
if status.teardown_allowed:
with self._context.test_teardown(result):
failure = self._run_teardown(test.keywords.teardown, status,
result)
if failure and result.critical:
status.critical_failure_occurred()
if not status.failures and result.timeout and result.timeout.timed_out():
status.test_failed(result.timeout.get_message())
result.message = status.message
result.status = status.status
result.endtime = get_timestamp()
self._output.end_test(ModelCombiner(test, result))
self._context.end_test(result)
def _add_exit_combine(self):
exit_combine = ('NOT robot-exit', '')
if exit_combine not in self._settings['TagStatCombine']:
self._settings['TagStatCombine'].append(exit_combine)
def _get_timeout(self, test):
if not test.timeout:
return None
return TestTimeout(test.timeout.value, test.timeout.message,
self._variables)
def _run_setup(self, setup, status, result=None):
if not status.failures:
exception = self._run_setup_or_teardown(setup)
status.setup_executed(exception)
if result and isinstance(exception, PassExecution):
result.message = exception.message
def _run_teardown(self, teardown, status, result=None):
if status.teardown_allowed:
exception = self._run_setup_or_teardown(teardown)
status.teardown_executed(exception)
failed = not isinstance(exception, PassExecution)
if result and exception:
result.message = status.message if failed else exception.message
return exception if failed else None
def _run_setup_or_teardown(self, data):
if not data:
return None
try:
name = self._variables.replace_string(data.name)
except DataError as err:
if self._settings.dry_run:
return None
return err
if name.upper() in ('', 'NONE'):
return None
try:
StepRunner(self._context).run_step(data, name=name)
except ExecutionFailed as err:
return err
这一部分看起来是最核心的执行测试的部分,下次继续
- Robot Framework 源代码阅读笔记 之 一
- Robot Framework 源代码阅读笔记 之二
- Robot Framework 源代码阅读笔记 之三
- Robot Framework 源代码阅读笔记 之四
- Robot Framework 源代码阅读笔记 之五
- Robot Framework 学习笔记(一)
- Robot Framework之环境搭建(一)
- robot-framework 源码阅读 之 suite name 搜索匹配
- z4root源代码阅读笔记一
- Mantle源代码阅读笔记 一
- Robot framework笔记1
- Robot Framework安装笔记
- Robot Framework之Return_From_Keyword
- flask源代码阅读笔记(一)
- Robot Framework项目实战笔记
- robot framework 使用一:win7上搭建robot framework环境
- 【Robot Framework】之 知识补充
- Robot Framework之元素定位
- JavaScript 动画之拖拽
- 赛车PK10/888雪球屠龙追号攻略技巧∈
- 学习笔记:PyQuery
- 大数据常见端口汇总-hadoop、hbase、hive、spark、kafka、zookeeper等(持续更新)
- Java集合的序列化
- Robot Framework 源代码阅读笔记 之 一
- 解决git clone时报错:The requested URL returned error: 401 Unauthorized while accessing
- jQuery框架
- Python 多CPU并行处理数据
- Qt 编码问题QTextCodec
- 对SIFT算法的理解,尤其是尺度不变性
- 用Maven中实现MyBatis逆向工程(IDEA版)
- 直接排序、选择排序(Java实现)
- java中的运算符