RobotFramework源码学习(一)

一、启动RIDE,运行方式是pybot;

二、python安装目录C:\Python27\Scripts可以找到pybot.bat文件,该路径是在环境变量中有配置的;

@echo off
python -m robot.run %*

 pybot.bat文件只两行代码,以不打印命令的方式执行该批处理文件;以“python -m robot.run %*”表示以脚本方式运行模块robot.run,关于脚本方式运行可参考文档“http://www.pythondoc.com/pythontutorial3/modules.html”。此时 __name__ 被设置为 "__main__";以脚本方式运行模块robot.run,从入口if __name__ == '__main__':开始。

三、打开robot/run.py,路径C:\Python27\Lib\site-packages\robot。找到入口函数:

if __name__ == '__main__':
    run_cli(sys.argv[1:])

“python -m robot.run %*”的·“%*”参数传递给run_cli函数;

def run_cli(arguments):
    """Command line execution entry point for running tests.

    :param arguments: Command line arguments as a list of strings.

    For programmatic usage the :func:`run` function is typically better. It has
    a better API for that usage and does not call :func:`sys.exit` like this
    function.

    Example::

        from robot import run_cli

        run_cli(['--include', 'tag', 'path/to/tests.html'])
    """
    RobotFramework().execute_cli(arguments)

类 RobotFramework的定义也是在run.py文件中

class RobotFramework(Application):

  类RobotFramework继承类Application,根据import的内容,可知Application定义在robot.utils模块里;RobotFramework类获得了Application类所有的成员方法和成员变量,包括execute_cli方法。通过查看代码可知,RobotFramework类重写了父类的main方法。

四、打开robot/utils/application.py,可以查看到execute_cli函数:

    def execute_cli(self, cli_arguments):
        with self._logging():
            options, arguments = self._parse_arguments(cli_arguments)
            rc = self._execute(arguments, options)
        self._exit(rc)

 前面是参数解析,重点是_execute函数:

    def _execute(self, arguments, options):
        try:
            rc = self.main(arguments, **options)
        except DataError, err:
            return self._report_error(unicode(err), help=True)
        except (KeyboardInterrupt, SystemExit):
            return self._report_error('Execution stopped by user.',
                                      rc=STOPPED_BY_USER)
        except:
            error, details = get_error_details()
            return self._report_error('Unexpected error: %s' % error,
                                      details, rc=FRAMEWORK_ERROR)
        else:
            return rc or 0

 在该函数中调用了main方法,返回变量rc,上面提及RobotFramework类重写了父类的main方法,代码如下:前后都是日志的设置和打印,关键是中间三行:

(一)创建TestBuilder对象,执行build方法得到测试套件suite;

(二)测试套件设置;

(三)测试套件执行得到执行结果;

def main(self, datasources, **options):
        settings = RobotSettings(options)
        LOGGER.register_console_logger(width=settings['MonitorWidth'],
                                       colors=settings['MonitorColors'],
                                       markers=settings['MonitorMarkers'],
                                       stdout=settings['StdOut'],
                                       stderr=settings['StdErr'])
        LOGGER.info('Settings:\n%s' % unicode(settings))
        suite = TestSuiteBuilder(settings['SuiteNames'],
                                 settings['WarnOnSkipped'],
                                 settings['RunEmptySuite']).build(*datasources)
        suite.configure(**settings.suite_config)
        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

(一)创建TestBuilder对象,执行build方法得到测试套件suite。

打开robot/running/builder.py,查看TestSuiteBuilder代码,来看下测试套件是怎么样构建的。重点是这四个函数:

TestSuite由TestData构建,在TestData中通过Table数据结构,保存了用例(testcase_table),关键字(keyword_table),变量(variable_table),设置(setting_table),这里面还有一些比较细的东西。

    def build(self, *paths):
        if not paths:
            raise DataError('One or more source paths required.')
        if len(paths) == 1:
            return self._build_and_check_if_empty(paths[0])
        root = TestSuite()
        for path in paths:
            root.suites.append(self._build_and_check_if_empty(path))
        return root
    def _build_and_check_if_empty(self, path):
        builded = self._build_suite(self._parse(path))
        if not self._empty_suites_allowed and not builded.test_count:
                raise DataError("Suite '%s' contains no tests." % builded.name)
        builded.remove_empty_suites()
        return builded
    def _build_suite(self, data, parent_defaults=None):
        defaults = TestDefaults(data.setting_table, parent_defaults)
        suite = TestSuite(name=data.name,
                          source=data.source,
                          doc=unicode(data.setting_table.doc),
                          metadata=self._get_metadata(data.setting_table))
        for import_data in data.setting_table.imports:
            self._create_import(suite, import_data)
        self._create_setup(suite, data.setting_table.suite_setup)
        self._create_teardown(suite, data.setting_table.suite_teardown)
        for var_data in data.variable_table.variables:
            self._create_variable(suite, var_data)
        for uk_data in data.keyword_table.keywords:
            self._create_user_keyword(suite, uk_data)
        for test_data in data.testcase_table.tests:
            self._create_test(suite, test_data, defaults)
        for child in data.children:
            suite.suites.append(self._build_suite(child, defaults))
        return suite

 def _parse(self, path):
        try:
            return TestData(source=abspath(path),
                            include_suites=self.include_suites,
                            warn_on_skipped=self.warn_on_skipped)
        except DataError, err:
            raise DataError("Parsing '%s' failed: %s" % (path, unicode(err)))

测试套件suite是什么数据结构? 

 ——TestSuite类的引入方式是“from .model import TestSuite, ForLoop”,也就是目录为C:\Python27\Lib\site-packages\robot\running\model,

从类TestSuite的代码来看,它继承自model.TestCase,路径是C:\Python27\Lib\site-packages\robot\model\testsuite.py。从以下代码可知,测试套件suite的数据结构是列表。

TestSuite构造方法,调用了父类的构造方法:

    def __init__(self,  name='', doc='', metadata=None, source=None):

        model.TestSuite.__init__(self, name, doc, metadata, source)
        #: Imports the suite contains.
        self.imports = []
        #: User keywords defined in the same file as the suite.
        #: **Likely to change or to be removed.**
        self.user_keywords = []
        #: Variables defined in the same file as the suite.
        #: **Likely to change or to be removed.**
        self.variables = []

model.TestSuite构造函数

    def __init__(self, name='', doc='', metadata=None, source=None):
        #: Parent :class:`TestSuite` or `None`.
        self.parent = None
        #: Test suite name.
        self.name = name
        #: Test suite documentation.
        self.doc = doc
        #: Test suite metadata as a dictionary.
        self.metadata = metadata
        #: Path to the source file or directory.
        self.source = source
        #: A list of child :class:`~.testsuite.TestSuite` instances.
        self.suites = []
        #: A list of :class:`~.testcase.TestCase` instances.
        self.tests = []
        #: A list containing setup and teardown as
        #: :class:`~keyword.Keyword` instances.
        self.keywords = []
        self._my_visitors = []

 (二)测试套件设置;

调用了C:\Python27\Lib\site-packages\robot\model\testsuite.py文件中TestSuite类的configure方法:

    def configure(self, **options):
        self.visit(SuiteConfigurer(**options))
    def visit(self, visitor):
        visitor.visit_suite(self)

在看下C:\Python27\Lib\site-packages\robot\model\configurer文件中的visit_suite方法,从方法可以看到是设置了测试套件,测试用例,筛选标签的一些内容。

    def visit_suite(self, suite):
        self._set_suite_attributes(suite)
        self._filter(suite)
        suite.set_tags(self.add_tags, self.remove_tags)

(三)测试套件执行得到执行结果;

 def run(self, settings=None, **options):
        """Executes the suite based based the given ``settings`` or ``options``.

        :param settings: :class:`~robot.conf.settings.RobotSettings` object
            to configure test execution.
        :param options: Used to construct new
            :class:`~robot.conf.settings.RobotSettings` object if ``settings``
            are not given.
        :return: :class:`~robot.result.executionresult.Result` object with
            information about executed suites and tests.

        If ``options`` are used, their names are the same as long command line
        options except without hyphens, and they also have the same semantics.
        Options that can be given on the command line multiple times can be
        passed as lists like ``variable=['VAR1:value1', 'VAR2:value2']``.
        If such an option is used only once, it can be given also as a single
        string like ``variable='VAR:value'``.

        Only options related to the actual test execution have an effect.
        For example, options related to selecting test cases or creating
        logs and reports are silently ignored. The output XML generated
        as part of the execution can be configured, though, including
        disabling it with ``output=None``.

        Example::

            result = suite.run(variable='EXAMPLE:value',
                               critical='regression',
                               output='example.xml',
                               exitonfailure=True,
                               skipteardownonexit=True)
            print result.return_code

        To save memory, the returned
        :class:`~robot.result.executionresult.Result` object object does not
        have any information about the executed keywords. If that information
        is needed, the created output XML file needs to be read  using the
        :class:`~robot.result.resultbuilder.ExecutionResult` factory method.

        See the :mod:`package level <robot.running>` documentation for
        more examples, including how to construct executable test suites and
        how to create logs and reports based on the execution results.
        """
        STOP_SIGNAL_MONITOR.start()
        IMPORTER.reset()
        settings = settings or RobotSettings(options)
        pyloggingconf.initialize(settings['LogLevel'])
        init_global_variables(settings)
        output = Output(settings)
        runner = Runner(output, settings)
        self.visit(runner)
        output.close(runner.result)
        return runner.result

 1、关键代码第一句是runner=Runner(output,settings)根据import内容定位到C:\Python27\Lib\site-packages\robot\running\runner.py,这个Runner类定义了很多执行用例的关键方法。

2、关键代码第二句是 self.visit(runner),这和

调用了C:\Python27\Lib\site-packages\robot\model\testsuite.py文件中TestSuite类的visit方法,在这里,入参visitor传入的是runner对象。

    def visit(self, visitor):
        visitor.visit_suite(self)

Runner类继承自SuiteVisitor类,从它的父类里找到了visit_suite方法,根据SuiteVisitor的导入路径定位到C:\Python27\Lib\site-packages\robot\model\visitor.py

class SuiteVisitor(object):

    def visit_suite(self, suite):
        if self.start_suite(suite) is not False:
            suite.keywords.visit(self)
            suite.suites.visit(self)
            suite.tests.visit(self)
            self.end_suite(suite)

 到这里就可以明确,点击RUN执行用例及之后发生什么事了

1.访问关键字;

2.访问测试套件;

2.执行用例套件下的测试用例;

猜你喜欢

转载自www.cnblogs.com/sanyun/p/10442317.html