Robot Framework源码解析(2) - 执行测试的入口点
我们再来看 src/robot/run.py 的工作原理。摘录部分代码:
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, text class RobotFramework(Application): def __init__(self):
Application.__init__(self, USAGE, arg_limits=(1,),
env_options='ROBOT_OPTIONS', logger=LOGGER) def main(self, datasources, **options):
...... def run_cli(arguments=None, exit=True):
if arguments is None:
arguments = sys.argv[1:]
return RobotFramework().execute_cli(arguments, exit=exit) def run(*tests, **options):
return RobotFramework().execute(*tests, **options) if __name__ == '__main__':
run_cli(sys.argv[1:])
在上一章我们提到Java的命令行入口其实最终还是转到了其它入口点,例如robot.run的run_cli(mytests.robot)
这里就先看第51行的run_cli方法 ,方法很简单,只是调用了RobotFramework类中的execute_cli方法。RobotFramework是run.py的一个内部类,也是Application的子类。通过第6行的 from robot.utils import Application可查看Application是做什么的。
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 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 _parse_arguments(self, cli_args):
try:
options, arguments = self.parse_arguments(cli_args)
except Information as msg:
self._report_info(msg.message)
except DataError as err:
self._report_error(err.message, help=True, exit=True)
else:
self._logger.info('Arguments: %s' % ','.join(arguments))
return options, arguments def parse_arguments(self, cli_args):
"""Public interface for parsing command line arguments. :param cli_args: Command line arguments as a list
:returns: options (dict), arguments (list)
:raises: :class:`~robot.errors.Information` when --help or --version used
:raises: :class:`~robot.errors.DataError` when parsing fails
"""
return self._ap.parse_args(cli_args) def execute(self, *arguments, **options):
with self._logger:
self._logger.info('%s %s' % (self._ap.name, self._ap.version))
return self._execute(list(arguments), options) 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的execute_cli方法,其实也只是做了参数的解析工作(请看第17行 和 第18行的方法调用),具体的任务如何执行交给了本实例的main方法(第50行)。那么仍然回到 src/robot/run.py 看RobotFramework的main方法:
def main(self, datasources, **options):
settings = RobotSettings(options)
LOGGER.register_console_logger(**settings.console_output_config)
LOGGER.info('Settings:\n%s' % unic(settings))
builder = TestSuiteBuilder(settings['SuiteNames'],
extension=settings.extension,
rpa=settings.rpa)
suite = builder.build(*datasources)
settings.rpa = builder.rpa
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):
old_max_error_lines = text.MAX_ERROR_LINES
text.MAX_ERROR_LINES = settings.max_error_lines
try:
result = suite.run(settings)
finally:
text.MAX_ERROR_LINES = old_max_error_lines
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
在这个方法里,进行了设置项的赋值(第2行),真正执行测试并输出测试结果(第18行)。通过第5,8,18行可以看到测试的执行过程首先是通过TestSuiteBuilder构建了一个suite,然后执行该suite的run方法。那么我们来看看TestSuiteBuilder是如何构建一个suite的。
通过from robot.running import TestSuiteBuilder可以知道TestSuiteBuilder是在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
由第1行可以看出TestSuiteBuilder在 src/robot/running/builder.py:摘录的部分代码:
class TestSuiteBuilder(object): def __init__(self, include_suites=None, warn_on_skipped='DEPRECATED',
extension=None, rpa=None):
self.include_suites = include_suites
self.extensions = self._get_extensions(extension)
builder = StepBuilder()
self._build_steps = builder.build_steps
self._build_step = builder.build_step
self.rpa = rpa
self._rpa_not_given = rpa is None
# TODO: Remove in RF 3.2.
if warn_on_skipped != 'DEPRECATED':
warnings.warn("Option 'warn_on_skipped' is deprecated and has no "
"effect.", DeprecationWarning)
...... 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))
root.rpa = self.rpa
return root
......
build方法的最后返回了一个TestSuite对象。走到这里好像有点太快了,为了更好的理解这个TestSuite,我们回过头来,顺藤摸瓜看看这个build的参数paths是什么: def build(self, *paths)(builder.py) <-- builder.build(*datasources) (run.py)<-- def main(self, datasources, **options): <-- self.main(arguments, **options)(Application.py) <-- def _execute(self, arguments, options): <-- self._execute(arguments, options) <-- def execute_cli(self, cli_arguments, exit=True):(Application.py) <--RobotFramework().execute_cli(arguments, exit=exit)(run.py) <-- def run_cli(arguments=None, exit=True):(run.py)
原来这个paths是命令后选项参数或者是方法调用时传递过来的参数。例如
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'])
或者 像第一篇文章中 java -jar robotframework.jar run mytests.robot这个命令,经过JarRunner解析会最终调用robot.run的run_cli("mytests.robot")这个方法
所以这个TestSuiteBuilder的目的是通过解析datasource来构建一个TestSuite ,接着回到builder.py的 build方法最后的TestSuite对象上,来看看TestSuite什么。
通过robot.running的_init_.py :from .model import Keyword, TestCase, TestSuite,可以看出TestSuite在 src/robot/running/model.py,摘录有关TestSuite的代码:
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, rpa=False):
model.TestSuite.__init__(self, name, doc, metadata, source, rpa)
#: :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 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
通过第10行的__init__方法可以看到,TestSuite初始化的时候包括name,doc,metadata,import,Variabel等数据。通过同一个图片我想大家应该就可以更 好的理解这里封装的信息了:
是的,就是这个可视化工具RIDE里的信息.当然这个类里面封装的信息并不全,因为它是model.TestSuite的子类,在父类中封装了更多的信息。
仍然回到 src/robot/run.py 的main方法,suite构建后会调用suite.run方法收集result。看 TestSuite类的第31行 self.visit(runner),这个visit方法都做了写什么?参数runner有时什么呢? 我们先在父类中看visit方法
def visit(self, visitor):
""":mod:`Visitor interface <robot.model.visitor>` entry-point."""
visitor.visit_suite(self)
方法很简单,只是去调用这个runner参数的visit_suite()方法。我们通过TestSuite类run方法中的from .runner import Runner可以知道 这个runner参数是:
class Runner(SuiteVisitor): 3 。。。。。。
Runner是SuiteVisitor的子类,里面并没有visit_suite 方法。去看看SuiteVisitor。 通过model包__init__.py的 from .visitor import SuiteVisitor 可知,SuiteVisitor在 /src/robot/model/visitor.py,摘录部分代码:
class SuiteVisitor(object):
"""Abstract class to ease traversing through the test suite structure. See the :mod:`module level <robot.model.visitor>` documentation for more
information and an example.
""" def visit_suite(self, suite):
"""Implements traversing through the suite and its direct children. Can be overridden to allow modifying the passed in ``suite`` without
calling :func:`start_suite` or :func:`end_suite` nor visiting child
suites, tests or keywords (setup and teardown) at all.
"""
if self.start_suite(suite) is not False:
suite.keywords.visit(self)
suite.suites.visit(self)
suite.tests.visit(self)
self.end_suite(suite) def start_suite(self, suite):
"""Called when suite starts. Default implementation does nothing. Can return explicit ``False`` to stop visiting.
"""
pass def end_suite(self, suite):
"""Called when suite ends. Default implementation does nothing."""
pass
在visit_suite方法中,开始了测试的执行,start_suite,end_suite 都在Runner具体实现. 今天先写到这里,下一章再接着分析visit_suite()里调用的各个方法的具体实现.
如果喜欢作者的文章,请关注"写代码的猿"订阅号以便第一时间获得最新内容。本文版权归作者所有,欢迎转载.
Robot Framework源码解析(2) - 执行测试的入口点的更多相关文章
- Robot Framework 源码解析(1) - java入口点
一直很好奇Robot Framework 是如何通过关键字驱动进行测试的,好奇它是如何支持那么多库的,好奇它是如何完成截图的.所以就打算研究一下它的源码. 这是官方给出的Robot framework ...
- MySQL源码解析之执行计划
MySQL源码解析之执行计划 MySQL执行计划介绍 MySQL执行计划代码概览 MySQL执行计划总结 一.MySQL执行计划介绍 在MySQL中,执行计划的实现是基于JOIN和QEP_TAB这两个 ...
- Robot Framework 源码阅读 day1 run.py
robot里面run起来的接口主要有两类 run_cli def run_cli(arguments): """Command line execution entry ...
- 【原创】angularjs1.3.0源码解析之执行流程
Angular执行流程 前言 发现最近angularjs在我厂的应用变得很广泛,下周刚好也有个angular项目要着手开始做,所以先做了下功课,从源代码开始入手会更深刻点,可能讲的没那么细,侧重点在于 ...
- Robot Framework 源码阅读 day2 TestSuitBuilder
接上一篇 day1 run.py 发现build test suit还挺复杂的, 先从官网API找到了一些资料,可以看出这是robotframework进行组织 测试案例实现的重要步骤, 将传入的te ...
- Robot Framework 源码阅读 day1 __main__.py
robot文件夹下的__main__.py函数 是使用module运行时的入口函数: import sys # Allows running as a script. __name__ check n ...
- mongo源码学习(四)服务入口点ServiceEntryPoint
在上一篇博客mongo源码学习(三)请求接收传输层中,稍微分析了一下TransportLayer的作用,这篇来看下ServiceEntryPoint是怎么做的. 首先ServiceEntryPoint ...
- Mybatis 系列10-结合源码解析mybatis 的执行流程
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列4-结合源码解析节点:typeAliases
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
随机推荐
- 动态规划:给出两个字符串s1和s2,返回其中最大的公共子串
求公共子字符串问题(连续的) 这个题目是当时远景能源公司现场笔试的一道题目,当时根本就不知道动态规划是什么鬼,直接上来就暴力求解,面试官很谄媚的问我,你这能求出来吗?当时很年轻的说,能啊!现在想,当时 ...
- 记住这个网站:服务器相关数据统计网站 http://news.netcraft.com/
http://news.netcraft.com/ 需要参考现在服务器相关数据,可以上这个网站. 当然google趋势也是一个可选得备案. 有一个数据统计更全面的: http://w3techs.co ...
- Python教程大纲
缘起:最近想在部门推Python语言,写这个blog主要就是个教程大纲,之前先列出一些资源:Python历史:http://www.docin.com/p-53019548.html ...
- App免费推广途径概要
说在前面的话:免费其实挺花功夫的,所有的营销的前提是产品和服务是值得推荐的. 1.技术操作维度:ASO,SEO, ASO简单介绍:http://baike.baidu.com/subview/1368 ...
- webService(一)开篇
Webservice技术在web开发中算是一个比较常见技术.这个对于大多数的web开发者,别管是Java程序员还是.NET程序员应该都不是很陌生.今天我就和大家一起来学习一下webservice的基本 ...
- SVD的概念以及应用
第十四章 利用SVD简化数据 一.引言 SVD的全称是奇异值分解,SVD的作用是它能够将高维的数据空间映射到低维的数据空间,实现数据约减和去除噪声的功能. SVD的特点主要有以下几个方面: 1.它的优 ...
- Java基础:Java虚拟机(JVM)
当我们第一次学习Java时这些原理上的东西就会被提到,但是很少有真正去学习.今天开始从头过一遍Java,打算从JVM开始. 1. JVM是什么 2. JRE和JDK 3. JVM结构 3.1. 程序计 ...
- PHP 7 新特性
PHP 7 新特性 标量类型声明 PHP 7 中的函数的形参类型声明可以是标量了.在 PHP 5 中只能是类名.接口.array 或者 callable (PHP 5.4,即可以是函数,包括匿名函数) ...
- 基于ubuntu16.04部署IBM开源区块链项目-弹珠资产管理(Marbles)
前言 本教程基本上是对Marbles项目的翻译过程. 如果英文比较好的话,建议根据官方操作说明,一步步进行环境部署.当然你也可以参考本教程在自己的主机上部署该项目. Marbles 介绍 关于 Mar ...
- Spring-cloud (八) Hystrix 请求缓存的使用
前言: 最近忙着微服务项目的开发,脱更了半个月多,今天项目的初版已经完成,所以打算继续我们的微服务学习,由于Hystrix这一块东西好多,只好多拆分几篇文章写,对于一般对性能要求不是很高的项目中,可以 ...