美文网首页
Robot Framework 源码解析(2)- 执行测试的入

Robot Framework 源码解析(2)- 执行测试的入

作者: 享受健身和阅读 | 来源:发表于2019-04-01 16:20 被阅读0次

    接着上一章,我们先来看 src/robot/run.py 的 run_cli方法。

    def run_cli(arguments=None, exit=True):

       if arguments is None:

            arguments = sys.argv[1:]

        return RobotFramework().execute_cli(arguments, exit=exit)

    方法很简单,只是调用了RobotFramework类中的execute_cli方法。RobotFramework是run.py的一个内部类,也是Application的子类。通过from robot.utils import Application, unic, text可查看Application是做什么的。

    src/robot/utils/application.py

    摘录部分代码:

    class Application(object):

    ......

        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)

           .....

    Application的execute_cli方法其实也只是做了参数的解析工作,具体的任务交给了本实例的main方法。仍然回到 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

    在这个方法里,进行了设置项的赋值,并且真正开始了执行测试。看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

    ......

        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

      ......

    这个TestSuiteBuilder的目的是通过解析datasource来构建一个TestSuite。那TestSuite又是什么的呢?

    从from .model import Keyword, TestCase, TestSuite可知,TestSuite是在

    src/robot/running/model.py

    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 configure(self, randomize_suites=False, randomize_tests=False,

                      randomize_seed=None, **options)

            model.TestSuite.configure(self, **options)

            self.randomize(randomize_suites, randomize_tests, randomize_seed)

        def randomize(self, suites=True, tests=True, seed=None):

            """Randomizes the order of suites and/or tests, recursively.

            :param suites: Boolean controlling should suites be randomized.

            :param tests: Boolean controlling should tests be randomized.

            :param seed: Random seed. Can be given if previous random order needs

                to be re-created. Seed value is always shown in logs and reports.

            """

            self.visit(Randomizer(suites, tests, 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

    这里TestSuite是model.TestSuite的子类

    /src/robot/model/testsuite.py

    def visit(self, visitor):

            """:mod:`Visitor interface <robot.model.visitor>` entry-point."""

            visitor.visit_suite(self)

    这里只是调用了Runner的visit_suite方法,来看一下

    src/robot/running/runner.py

    class Runner(SuiteVisitor):

    Runner只是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)

      .......

    start_suite / end_suite 就是在Runner具体实现的。

    具体看start_suite是做什么的:

    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(),

                              rpa=self._settings.rpa)

            if not self.result:

                result.set_criticality(self._settings.critical_tags,

                                      self._settings.non_critical_tags)

                self.result = Result(root_suite=result, rpa=self._settings.rpa)

                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 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()

    执行到这里,会根据设置和datasource 已经开始了收集测试结果。

    回到最初的 src/robot/run.py

    根据

    if __name__ == '__main__':

        run_cli(sys.argv[1:])

    可以看出,run.py不仅可以通过java -jar robotframework.jar run mytests.robot,被最终调用,还可以直接使用命令,例如:

            robot path/to/tests.robot

            robot --include tag1 --include tag2 --splitlog tests.robot

            robot --name Example --log NONE t1.robot t2.robot > stdout.txt

    来执行robotframework的测试用例。

    如果喜欢作者的文章,请关注"写代码的猿"订阅号以便第一时间获得最新内容。本文版权归作者所有,欢迎转载. 

    相关文章

      网友评论

          本文标题:Robot Framework 源码解析(2)- 执行测试的入

          本文链接:https://www.haomeiwen.com/subject/strcbqtx.html