この記事は 「Robot Framework Advent Calendar 2017 - Qiita」 の17日目の記事です。
Robot Frameworkの組込キーワード Evaluate
を使うとPythonコードを実行できます。
- http://robotframework.org/robotframework/latest/libraries/BuiltIn.html#Evaluate
- http://robotframework-ja.readthedocs.io/ja/latest/lib/BuiltIn.html#evaluate
今回は Evaluate
キーワードを試してみます。
目次
環境
- Python 3.6.3
- Robot Framework 3.0.2
- Robotframework-DebugLibrary 1.0.2
- Robot FrameworkのREPLで確認しながら進めるために使用
- 詳しくは前回の記事を参照
文字列のフォーマット
Robot Framework単独では難しいのが文字列のフォーマットです。
一方、Pythonで書くと楽です。
今回は
- %演算子
- str.format()関数
- f-strings
- Python3.6以上
を試します。
Robotframework-DebugLibraryの rfdebug
コマンドでREPLを起動し、それぞれを確認してみます。
$ rfdebug ==================== Robot Debug9 R36Le9 ==================== RFDEBUG REPL >>>>> Enter interactive shell Only accepted plain text format keyword seperated with two or more spaces. Type "help" for more information. > ${結果} = Evaluate '%06d %s' % (1, 'ゼロ埋めです') # ${結果} = '000001 ゼロ埋めです' > ${結果} = Evaluate 'foo {}'.format('bar') # ${結果} = 'foo bar' > ${結果2} = Evaluate 'foo {}'.format('bar') # ${結果2} = 'foo bar' > ${結果3} = Evaluate f'baz ${結果2}' # ${結果3} = 'baz foo bar'
いずれも動作しました。
Pythonクラスのインスタンスを取得する
Evaluateのソースコードを読むと、内部ではPythonの eval()
関数を使っています。
https://github.com/robotframework/robotframework/blob/3.0.2/src/robot/libraries/BuiltIn.py#L2963
また、Evaluateの引数のmoduleやnamespaceは __import__()
関数でimportされています。
https://github.com/robotframework/robotframework/blob/3.0.2/src/robot/libraries/BuiltIn.py#L2997
そのため、Pythonクラスのインスタンスを取得できそうです。
Robot FrameworkのLibraryで対象モジュールをimport済の場合
sys.modulesを使う
例えば、
*** Settings *** Library SeleniumLibrary Library DebugLibrary *** TestCases *** 読み込み済モジュールに含まれるクラスをインスタンス化する Debug
とした時に、
- SeleniumLibraryをimport済
- Headless Firefoxの設定を行うために
selenium.webdriver.firefox.options.Option
クラスのインスタンスを取得したい
とします。
この場合、 sys.modules
に selenium
パッケージが追加されています。
- sys.modules - 29.1. sys — システムパラメータと関数 — Python 3.6.3 ドキュメント
- 6.4. sys.modules を使う - Dive into Python 5.4. (JAPANESE)
そのため、
${options} = Evaluate sys.modules['selenium.webdriver.firefox.options'].Options() sys
としてOptionsクラスのインスタンスを取得できます。
なお、引数は
- 第1引数
sys.modules
のselenium.webdriver.firefox.options
にある Options クラスをインスタンス化する処理
- 第2引数
- 第1引数で使われているモジュールのうち、追加でimportが必要なモジュール名
- 今回は
sys
モジュールを使用
- 今回は
- 第1引数で使われているモジュールのうち、追加でimportが必要なモジュール名
です。
本当に取得できたのか、REPLで確認してみます。
# 上記例が含まれるRobot Frameworkのテストケースを実行 $ robot test_get_instance_from_already_imported_module.robot ... # キーワードDebugに到達したため、REPLが起動 >>>>> Enter interactive shell Only accepted plain text format keyword seperated with two or more spaces. Type "help" for more information. # Robot Frameworkライブラリの確認:SeleniumLibraryあり > libs < Imported libraries: BuiltIn 3.0.2 An always available standard library with often needed keywords. DebugLibrary 1.0.2 Debug Library for RobotFramework Easter Reserved SeleniumLibrary 3.0.1 SeleniumLibrary is a web testing library for Robot Framework. < Bultin libraries: BuiltIn Collections DateTime Dialogs Easter OperatingSystem Process Remote Reserved Screenshot String Telnet XML # sys.modulesにあるか確認:あった > Evaluate sys.modules['selenium.webdriver.firefox.options'] sys < <module 'selenium.webdriver.firefox.options' from '/path/to/rfenv363/lib/python3.6/site-packages/selenium/webdriver/firefox/options.py'> # 上記のコードを試す:インスタンスを取得できた > ${options} = Evaluate sys.modules['selenium.webdriver.firefox.options'].Options() sys # ${options} = <selenium.webdriver.firefox.options.Options object at 0x10eee60b8>
Robot Frameworkを対象モジュールをimportしていない場合
例えば、
*** Settings *** Library DebugLibrary *** TestCases *** 読み込まれていないモジュールに含まれるクラスをインスタンス化する Debug
とした時に、
- バージョンを比較するために
distutils.version.LooseVersion
クラスのインスタンスを取得したい
とします。
sys.modulesを使う場合
sys.modulesを使う場合は、Libraryでimportした時と同じです。
# 上記のテストケースを実行 $ robot test_get_instance_from_no_import_module.robot ... # キーワードDebugに到達したため、REPLが起動 >>>>> Enter interactive shell Only accepted plain text format keyword seperated with two or more spaces. Type "help" for more information. # sysとdistutils.versionをimportして、LooseVersionクラスのインスタンスを取得 > Evaluate sys.modules['distutils.version'].LooseVersion('3.0.2') sys,distutils.version < LooseVersion ('3.0.2')
__import__()関数を使う場合
Pythonでのimport同様、 __import__()
関数も使えます。
__import__ - 2. 組み込み関数 — Python 3.6.3 ドキュメント
ただ、
name 変数が package.module 形式であるとき、通常は、name で指名されたモジュール ではなく、最上位のパッケージ (最初のドットまでの名前) が返されます。しかしながら、空でない fromlist 引数が与えられると、 name で指名されたモジュールが返されます。
のため、第1引数だけ指定すると
$ robot test_get_instance_from_no_import_module.robot ... > Evaluate __import__('distutils.version').LooseVersion('3.0.2') ! keyword: Evaluate __import__('distutils.version').LooseVersion('3.0.2') ! Evaluating expression '__import__('distutils.version').LooseVersion('3.0.2')' failed: AttributeError: module 'distutils' has no attribute 'LooseVersion'
と、エラーになります。
そのため、 fromlist
引数も使用します。
$ robot test_get_instance_from_no_import_module.robot ... > Evaluate __import__('distutils.version', fromlist=['LooseVersion']).LooseVersion('3.0.2') < LooseVersion ('3.0.2')
importlib.import_module()を使う場合
__import__() 関数の説明の中で
__import__() を直接使用することも推奨されず、 importlib.import_module() の方が好まれます。
とあったため、 importlib.import_module()
を見てみます。
importlib.import_module()の説明には
import_module() 関数は importlib.__import__() を単純化するラッパーとして働きます。つまり、この関数のすべての意味は importlib.__import__() から受け継いでいます。これらの2つの関数の最も重要な違いは、 import_module() が指定されたパッケージやモジュール (例えば pkg.mod) を返すのに対し、 __import__() はトップレベルのパッケージやモジュール (例えば pkg) を返すことです。
importlib.import_module(name, package=None) - 31.5. importlib — import の実装 — Python 3.6.3 ドキュメント
とありました。
試してみます。
$ robot test_get_instance_from_no_import_module.robot ... > Evaluate importlib.import_module('distutils.version').LooseVersion('3.0.2') importlib < LooseVersion ('3.0.2')
同じ結果になりました。
個人的には __import__()
よりも importlib.import_module()
の方が分かりやすいです。
ソースコード
GitHubに上げました。builtin_library_samples/evaluate
ディレクリの中が今回のものです。
thinkAmi-sandbox/RobotFramework-sample: Robot Framewrok samples