Robot Framework源碼解析(2) - 執行測試的入口點


我們再來看 src/robot/run.py 的工作原理。摘錄部分代碼:

 1 from robot.conf import RobotSettings
 2 from robot.model import ModelModifier
 3 from robot.output import LOGGER, pyloggingconf
 4 from robot.reporting import ResultWriter
 5 from robot.running import TestSuiteBuilder
 6 from robot.utils import Application, unic, text
 7 
 8 class RobotFramework(Application):
 9 
10     def __init__(self):
11         Application.__init__(self, USAGE, arg_limits=(1,),
12                              env_options='ROBOT_OPTIONS', logger=LOGGER)
13 
14     def main(self, datasources, **options):
15         ......
16 
17 def run_cli(arguments=None, exit=True):
18     if arguments is None:
19         arguments = sys.argv[1:]
20     return RobotFramework().execute_cli(arguments, exit=exit)
21 
22 
23 def run(*tests, **options):
24    return RobotFramework().execute(*tests, **options)
25 
26 
27 if __name__ == '__main__':
28     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

摘錄部分代碼:

 1 class Application(object):
 2 
 3     def __init__(self, usage, name=None, version=None, arg_limits=None,
 4                  env_options=None, logger=None, **auto_options):
 5         self._ap = ArgumentParser(usage, name, version, arg_limits,
 6                                   self.validate, env_options, **auto_options)
 7         self._logger = logger or DefaultLogger()
 8 
 9     def main(self, arguments, **options):
10         raise NotImplementedError
11 
12 ......
13 
14     def execute_cli(self, cli_arguments, exit=True):
15         with self._logger:
16             self._logger.info('%s %s' % (self._ap.name, self._ap.version))
17             options, arguments = self._parse_arguments(cli_arguments)
18             rc = self._execute(arguments, options)
19         if exit:
20             self._exit(rc)
21         return rc
22     def _parse_arguments(self, cli_args):
23         try:
24             options, arguments = self.parse_arguments(cli_args)
25         except Information as msg:
26             self._report_info(msg.message)
27         except DataError as err:
28             self._report_error(err.message, help=True, exit=True)
29         else:
30             self._logger.info('Arguments: %s' % ','.join(arguments))
31             return options, arguments
32 
33     def parse_arguments(self, cli_args):
34         """Public interface for parsing command line arguments.
35 
36         :param    cli_args: Command line arguments as a list
37         :returns: options (dict), arguments (list)
38         :raises:  :class:`~robot.errors.Information` when --help or --version used
39         :raises:  :class:`~robot.errors.DataError` when parsing fails
40         """
41         return self._ap.parse_args(cli_args)
42 
43     def execute(self, *arguments, **options):
44         with self._logger:
45             self._logger.info('%s %s' % (self._ap.name, self._ap.version))
46             return self._execute(list(arguments), options)
47 
48     def _execute(self, arguments, options):
49         try:
50             rc = self.main(arguments, **options)
51         except DataError as err:
52             return self._report_error(err.message, help=True)
53         except (KeyboardInterrupt, SystemExit):
54             return self._report_error('Execution stopped by user.',
55                                       rc=STOPPED_BY_USER)
56         except:
57             error, details = get_error_details(exclude_robot_traces=False)
58             return self._report_error('Unexpected error: %s' % error,
59                                       details, rc=FRAMEWORK_ERROR)
60         else:
61             return rc or 0

Application的execute_cli方法,其實也只是做了參數的解析工作(請看第17行 和 第18行的方法調用),具體的任務如何執行交給了本實例的main方法(第50行)。那么仍然回到 src/robot/run.py 看RobotFramework的main方法:

 1     def main(self, datasources, **options):
 2         settings = RobotSettings(options)
 3         LOGGER.register_console_logger(**settings.console_output_config)
 4         LOGGER.info('Settings:\n%s' % unic(settings))
 5         builder = TestSuiteBuilder(settings['SuiteNames'],
 6                                    extension=settings.extension,
 7                                    rpa=settings.rpa)
 8         suite = builder.build(*datasources)
 9         settings.rpa = builder.rpa
10         suite.configure(**settings.suite_config)
11         if settings.pre_run_modifiers:
12             suite.visit(ModelModifier(settings.pre_run_modifiers,
13                                       settings.run_empty_suite, LOGGER))
14         with pyloggingconf.robot_handler_enabled(settings.log_level):
15             old_max_error_lines = text.MAX_ERROR_LINES
16             text.MAX_ERROR_LINES = settings.max_error_lines
17             try:
18                 result = suite.run(settings)
19             finally:
20                 text.MAX_ERROR_LINES = old_max_error_lines
21             LOGGER.info("Tests execution ended. Statistics:\n%s"
22                         % result.suite.stat_message)
23             if settings.log or settings.report or settings.xunit:
24                 writer = ResultWriter(settings.output if settings.log
25                                       else result)
26                 writer.write_results(settings.get_rebot_settings())
27         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

1 from .builder import TestSuiteBuilder, ResourceFileBuilder
2 from .context import EXECUTION_CONTEXTS
3 from .model import Keyword, TestCase, TestSuite
4 from .testlibraries import TestLibrary
5 from .usererrorhandler import UserErrorHandler
6 from .userkeyword import UserLibrary
7 from .runkwregister import RUN_KW_REGISTER

由第1行可以看出TestSuiteBuilder在 src/robot/running/builder.py:摘錄的部分代碼:

 1 class TestSuiteBuilder(object):
 2 
 3     def __init__(self, include_suites=None, warn_on_skipped='DEPRECATED',
 4                  extension=None, rpa=None):
 5         self.include_suites = include_suites
 6         self.extensions = self._get_extensions(extension)
 7         builder = StepBuilder()
 8         self._build_steps = builder.build_steps
 9         self._build_step = builder.build_step
10         self.rpa = rpa
11         self._rpa_not_given = rpa is None
12         # TODO: Remove in RF 3.2.
13         if warn_on_skipped != 'DEPRECATED':
14             warnings.warn("Option 'warn_on_skipped' is deprecated and has no "
15                           "effect.", DeprecationWarning)
16 ......
17 
18     def build(self, *paths):
19         """
20         :param paths: Paths to test data files or directories.
21         :return: :class:`~robot.running.model.TestSuite` instance.
22         """
23         if not paths:
24             raise DataError('One or more source paths required.')
25         if len(paths) == 1:
26             return self._parse_and_build(paths[0])
27         root = TestSuite()
28         for path in paths:
29             root.suites.append(self._parse_and_build(path))
30         root.rpa = self.rpa
31         return root
32 ......

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是命令后選項參數或者是方法調用時傳遞過來的參數。例如

1 from robot import run_cli
2 
3 # Run tests and return the return code.
4 rc = run_cli(['--name', 'Example', 'tests.robot'], exit=False)
5 
6 # Run tests and exit to the system automatically.
7 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的代碼:

 1 class TestSuite(model.TestSuite):
 2     """Represents a single executable test suite.
 3 
 4     See the base class for documentation of attributes not documented here.
 5     """
 6     __slots__ = ['resource']
 7     test_class = TestCase    #: Internal usage only.
 8     keyword_class = Keyword  #: Internal usage only.
 9 
10     def __init__(self,  name='', doc='', metadata=None, source=None, rpa=False):
11         model.TestSuite.__init__(self, name, doc, metadata, source, rpa)
12         #: :class:`ResourceFile` instance containing imports, variables and
13         #: keywords the suite owns. When data is parsed from the file system,
14         #: this data comes from the same test case file that creates the suite.
15         self.resource = ResourceFile(source=source)
16 。。。。。。
17     def run(self, settings=None, **options):
18         from .namespace import IMPORTER
19         from .signalhandler import STOP_SIGNAL_MONITOR
20         from .runner import Runner
21 
22         with LOGGER:
23             if not settings:
24                 settings = RobotSettings(options)
25                 LOGGER.register_console_logger(**settings.console_output_config)
26             with pyloggingconf.robot_handler_enabled(settings.log_level):
27                 with STOP_SIGNAL_MONITOR:
28                     IMPORTER.reset()
29                     output = Output(settings)
30                     runner = Runner(output, settings)
31                     self.visit(runner)
32                 output.close(runner.result)
33         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方法

/src/robot/model/testsuite.py

1     def visit(self, visitor):
2         """:mod:`Visitor interface <robot.model.visitor>` entry-point."""
3         visitor.visit_suite(self)

方法很簡單,只是去調用這個runner參數的visit_suite()方法。我們通過TestSuite類run方法中的from .runner import Runner可以知道 這個runner參數是:

src/robot/running/runner.py

 1 class Runner(SuiteVisitor):
 2 
 3 。。。。。。

Runner是SuiteVisitor的子類,里面並沒有visit_suite 方法。去看看SuiteVisitor。 通過model包__init__.py的 from .visitor import SuiteVisitor 可知,SuiteVisitor在  /src/robot/model/visitor.py,摘錄部分代碼:

 1 class SuiteVisitor(object):
 2     """Abstract class to ease traversing through the test suite structure.
 3 
 4     See the :mod:`module level <robot.model.visitor>` documentation for more
 5     information and an example.
 6     """
 7 
 8     def visit_suite(self, suite):
 9         """Implements traversing through the suite and its direct children.
10 
11         Can be overridden to allow modifying the passed in ``suite`` without
12         calling :func:`start_suite` or :func:`end_suite` nor visiting child
13         suites, tests or keywords (setup and teardown) at all.
14         """
15         if self.start_suite(suite) is not False:
16             suite.keywords.visit(self)
17             suite.suites.visit(self)
18             suite.tests.visit(self)
19             self.end_suite(suite)
20 
21     def start_suite(self, suite):
22         """Called when suite starts. Default implementation does nothing.
23 
24         Can return explicit ``False`` to stop visiting.
25         """
26         pass
27 
28     def end_suite(self, suite):
29         """Called when suite ends. Default implementation does nothing."""
30         pass

在visit_suite方法中,開始了測試的執行,start_suite,end_suite 都在Runner具體實現. 今天先寫到這里,下一章再接着分析visit_suite()里調用的各個方法的具體實現.

 

如果喜歡作者的文章,請關注"寫代碼的猿"訂閱號以便第一時間獲得最新內容。本文版權歸作者所有,歡迎轉載. 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM