测试工作需要用到了RobotFramework,之前只是使用,用了一段时间后,决定深入研究一下它。使用平台为Windows + Linux。由于是基于Linux 系统方面的测试工作,使用的RobotFramework版本为2.8.5
先简单介绍一下Robot中的一些概念
关键字(Keyword): 分为内部关键字和用户关键字。
内部关键字顾名思义为Robot内置的部分关键字,例如Should Be Equal,Run Keyword 等等
用户关键字,开发人员或者测试人员自行通过内部关键字组合 OR 采用外部代码导入构成的关键字
资源(Resource): 关键字的组合包,例如将多个关键字放在一个文件里,这个文件在Robot里可以称为一个资源
库(Library): Robot支持很多库,分为内置和外置,内置的如Collections等,外置的,可以是python写成的文件,java写成的文件,以及通过xml-rpc导入的远程库,这些库一般都是通过代码写成
用例(Case):多个关键字的组合,顺序 or 带分支控制等组成的文件为Case
Suite:可以理解为多个用例的组合,一个suit 文件下可以写多条用例。
Windows的入口文件为pybot.bat
Linux的入口文件为pybot
由上图可以知道,robot的入口文件为robot un.py(可以查看robot\__init__.py中代码可知)
看一下run_cli()和run()的区别
从上图和下图的代码看,run_cli调用的是RobotFramework类下的execute_cli方法,run调用的是RobotFramework类下的execute方法,不同的是execute_cli多了一些参数解析的操作,所以推荐当代码中调用的时候,使用run_cli,而当工具调用的时候,使用run。
不管是run还是run_cli,核心都调用了_execute这个内部函数,而它又调用了main函数,这个main,在Application类中是一个需要被继承重写的函数,所以需要去继承它的类中寻找具体实现
1 def execute_cli(self, cli_arguments): 2 with self._logging(): 3 options, arguments = self._parse_arguments(cli_arguments) 4 rc = self._execute(arguments, options) 5 self._exit(rc) 6 7 def execute(self, *arguments, **options): 8 with self._logging(): 9 return self._execute(list(arguments), options) 10 11 def _execute(self, arguments, options): 12 try: 13 rc = self.main(arguments, **options) 14 except DataError, err: 15 return self._report_error(unicode(err), help=True) 16 except (KeyboardInterrupt, SystemExit): 17 return self._report_error('Execution stopped by user.', 18 rc=STOPPED_BY_USER) 19 except: 20 error, details = get_error_details() 21 return self._report_error('Unexpected error: %s' % error, 22 details, rc=FRAMEWORK_ERROR) 23 else: 24 return rc or 0
下面为main函数的具体实现,核心为5~9行,5行前面为初始化一些基础的配置,9行以后为生成测试log
1 def main(self, datasources, **options): 2 settings = RobotSettings(options) 3 LOGGER.register_console_logger(**settings.console_logger_config) 4 LOGGER.info('Settings: %s' % unicode(settings)) 5 suite = TestSuiteBuilder(settings['SuiteNames'], 6 settings['WarnOnSkipped'], 7 settings['RunEmptySuite']).build(*datasources) 8 suite.configure(**settings.suite_config) 9 result = suite.run(settings) 10 LOGGER.info("Tests execution ended. Statistics: %s" 11 % result.suite.stat_message) 12 if settings.log or settings.report or settings.xunit: 13 writer = ResultWriter(settings.output if settings.log else result) 14 writer.write_results(settings.get_rebot_settings()) 15 return result.return_code
以windows运行命令行为例 python run.py --outputdir C:\logs --test 初始化环境 C:\环境验证
5~7行,suite为TestSuiteBuilder根据Settings中的部分参数进行构建,并执行build函数,其中datasources为List类型的数据,即['C:\环境验证'],说明指定用例suit的时候,是可以同时指定多个Suit一起跑
9行,为执行指定suite(用例),并获取结果
看一下build具体执行操作
1 def build(self, *paths): 2 if not paths: 3 raise DataError('One or more source paths required.') 4 if len(paths) == 1: 5 return self._build_and_check_if_empty(paths[0]) 6 root = TestSuite() 7 for path in paths: 8 root.suites.append(self._build_and_check_if_empty(path)) 9 return root 10 def _build_and_check_if_empty(self, path): 11 builded = self._build_suite(self._parse(path)) 12 if not self._empty_suites_allowed and not builded.test_count: 13 raise DataError("Suite '%s' contains no tests." % builded.name) 14 builded.remove_empty_suites() 15 return builded 16 17 def _build_suite(self, data, parent_defaults=None): 18 defaults = TestDefaults(data.setting_table, parent_defaults) 19 suite = TestSuite(name=data.name, 20 source=data.source, 21 doc=unicode(data.setting_table.doc), 22 metadata=self._get_metadata(data.setting_table)) 23 for import_data in data.setting_table.imports: 24 self._create_import(suite, import_data) 25 self._create_setup(suite, data.setting_table.suite_setup) 26 self._create_teardown(suite, data.setting_table.suite_teardown) 27 for var_data in data.variable_table.variables: 28 self._create_variable(suite, var_data) 29 for uk_data in data.keyword_table.keywords: 30 self._create_user_keyword(suite, uk_data) 31 for test_data in data.testcase_table.tests: 32 self._create_test(suite, test_data, defaults) 33 for child in data.children: 34 suite.suites.append(self._build_suite(child, defaults)) 35 return suite
build根据传入的datasources的个数进行构建,对于单独的一个文件的情况下,调用了内部函数_build_and_check_if_empty,这个函数会判断传入的datasource是否真实存在,如果存在,会再次调用_build_suite进行suite构建
_parse()这个内部的函数完成了robot文件(.txt 或者.robot文件)到robot的数据结构TestData的转换, TestData数据结构会在后面的文章详细讲解
1 def _parse(self, path): 2 try: 3 return TestData(source=abspath(path), 4 include_suites=self.include_suites, 5 warn_on_skipped=self.warn_on_skipped) 6 except DataError, err: 7 raise DataError("Parsing '%s' failed: %s" % (path, unicode(err)))
回到_build_suite
data.setting_table.imports是指suite最开始定义的可以进行导入的Source, Libriary等
data.setting_table.suite_setup 是指在suite中定义的 Suite Setup,同理 data.setting_table.suite_teardown 是指 Suite Teardown
data.variable_table.variables 是指在suite中定义的变量
data.keyword_table.keywords 定义的关键字
data.testcase_table.tests 是指suite中定义的case,对test,会调用下面的代码进行创建test
1 def _create_test(self, suite, data, defaults): 2 values = defaults.get_test_values(data) 3 test = suite.tests.create(name=data.name, 4 doc=unicode(data.doc), 5 tags=values.tags.value, 6 template=self._get_template(values.template), 7 timeout=self._get_timeout(values.timeout)) 8 self._create_setup(test, values.setup) 9 for step_data in data.steps: 10 self._create_step(test, step_data, template=values.template) 11 self._create_teardown(test, values.teardown)
values是获取了单个test的一些具体信息,如tags,test Setup, Test Teardown等
suite.tests为 TestCase类型数据,TestSuite 继承于model.TestSuite, 而model.TestSuite 中通过装饰器为tests 赋予TestCase类型数据
1 @setter 2 def tests(self, tests): 3 return TestCases(self.test_class, self, tests)
后续代码创建了分解了整个case的各个步骤,通过_create_step对test的步骤进行扩充
到此为止,TestSuiteBuilder便构建完成
由于Case执行部分复杂,将在下一篇文章中详细讲解