接著上一章肢础,我們先來看?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方法其實也只是做了參數(shù)的解析工作害淤,具體的任務交給了本實例的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
在這個方法里卑雁,進行了設置項的賦值募书,并且真正開始了執(zhí)行測試〔舛祝看TestSuiteBuilder是做什么的莹捡。
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是在
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的子類
def visit(self, visitor):
? ? ? ? """:mod:`Visitor interface <robot.model.visitor>` entry-point."""
? ? ? ? visitor.visit_suite(self)
這里只是調用了Runner的visit_suite方法,來看一下
class Runner(SuiteVisitor):
Runner只是SuiteVisitor的一個子類琉挖,看看SuiteVisitor
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具體實現(xiàn)的启泣。
具體看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()
執(zhí)行到這里,會根據(jù)設置和datasource 已經開始了收集測試結果示辈。
回到最初的?src/robot/run.py
根據(jù)
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
來執(zhí)行robotframework的測試用例。
如果喜歡作者的文章险耀,請關注"寫代碼的猿"訂閱號以便第一時間獲得最新內容弄喘。本文版權歸作者所有,歡迎轉載.?