ネットワークテクノロジー課の大東です。
新卒でNW自動化開発チームに配属され、2年弱NW自動化開発業務中心に担当しています。
私事なのですが、最近この仕事をしていて良かったなと感じたエピソードがあります。
とある案件で、1ヶ月で何百台もの機器に対し出荷前の正常性動作確認を行う作業の自動化に携わりました。
出荷検査を目の前で見たことがなかったのですが、実際に自動化ツールを使った作業風景を見ると、納期が決められている中、作業スペースで1人が1日に何十台もの機器に対して同じ検査を繰り返していました。
あ〜これを手動でやろうとすると、作業者の気力・体力がすり減り、ミスも増えるだろうなと想像するだけで気が滅入りました。
それと同時に、自動化することで、RobotFrameworkが、試験を担保してくれるので、作業者の負担を軽減できていることや品質の向上に繋がっていると実感し、自動化ツールを作成して良かったと改めて感じました。
そして、この品質を高めるために重要なのが、Assertionだと筆者は思います。
試験の中身は、Assertionの集合であって、正しくAssertionされていることが重要になります。
また、どの段階でどうAssertionするかによって、コーディングミスを軽減したり、コード可読性を向上させることもできます。
今回は、そもそもAssertionってなんだろう?という方からでも理解できるように 「RobotFrameworkにおけるAssertion」についてご紹介させていただきます。
Assertion(アサーション)とは、プログラムにおいて、あるコードが実行されている時に満たされるべき条件を記述し、実行時にチェックする仕組みを指します。
つまり、Assertionとは試験の合否判定です。
特定の結果が想定された結果であることを確認して、テストが妥当であることを確認します。
Assertionの結果、Assertionが失敗した場合は、Errorや例外を発生させて処理を中断させることができます。
適切にAssertion(確認ポイント)を実行する事で、そのテストの妥当性が保証されます。
RobotFrameworkにはAssertion機能があります。
対象の値が、指定した条件を満たしているかを判定することが可能です。
対象の値が指定した条件と合致するかを評価して、Trueの場合はtestcaseがPASSし、Falseの場合はErrorをraiseしてtestcaseがFAILするといった挙動になります。
RobotFrameworkはAssertion機能として、Built-inのAssertion Keywordを提供しています。
例えば、 Should Be True 、 Should Be False 、 Should Be Equal 、 Should Not Be Equal などがあります。
キーワード名に Should が入っているものは、基本的には、Assertionをするキーワードになると覚えておくと良いでしょう。
自作ライブラリを作成する時に、この命名規則に倣って関数名をつけるとコードの可読性が向上します。
引数が真の時成功し、偽の時に失敗するキーワードになります。
引数には、 条件式 か 文字列でない要素( list や dict など)を指定できます。
条件式の場合はPythonの式として評価されます。
意外と、 Should Be True の後に式の評価ができること を知らない人が多いので覚えておくと良いでしょう。
式の評価とは、下記の例でいうと、 ${num} < 10 のようなPythonの条件式を評価することを指します。
文字列でない要素の場合は、Pythonにおける真偽値から直接判定します。
#条件式の場合
Should Be True ${num} < 10
#文字列でない場合
Should Be True ${list}
例えば、変数num1,num2の数値が10かどうかを確認する時のAssertionは下記のようになります。
*** Variables ***
${num1} 10
${num2} 5
*** Test Cases ***
test1
Should Be True ${num1} == 10
test2
Should Be True ${num2} == 10
実行結果から、Assertion機能によって、期待値の確認が実施され、結果としてRobotFramework上ではPASS/FAILで確認できることが分かります。
また、RobotFrameworkのAssertion機能は、一般的なプログラミングにおけるIF文の条件式と似ています。
上記のAssertionをPythonで書くと下のようになります。
if num != 10:
raise AssertionError("10ではありません")
Assertionをすることで、この試験結果はこうあるべきという確認を可能にしています。
これがRobotFrameworkでの基礎的なAssertionになります。
ここからは、`Should Be True`のコードの中身を見ながら、Assertion Keywordが何をしているか深掘りしていきましょう。
def should_be_true(self, condition, msg=None):
if not self._is_true(condition):
raise AssertionError(msg or "'%s' should be true." % condition)
まず、_is_true()を実行してそうですね。
_is_true()は条件の評価を実行している関数で、評価結果がTrueの時は何も実行せずに、Falseの時はAssertionErrorをraiseしていますね。
では、_is_true()の中身を見てみます。
def _is_true(self, condition):
if is_string(condition):
condition = self.evaluate(condition)
return bool(condition)
条件分岐はありますが、Error以外の場合は、evaluate()を実行しています。
evaluate()はRobotFrameworkのキーワードで式を評価してTrue/Falseを返すキーワードになります。
結局、Should Be TrueはEvaluateをラップしたキーワードだと分かりました。
では、evaluate()の中身を見てみます。
def evaluate(self, expression, modules=None, namespace=None):
try:
return evaluate_expression(expression, self._variables.current.store,
modules, namespace)
except DataError as err:
raise RuntimeError(err.message)
evaluate_expression()を実行してますね。
evaluate_expression()の中身を見てみます。
def evaluate_expression(expression, variable_store, modules=None, namespace=None):
try:
if not isinstance(expression, str):
raise TypeError(f'Expression must be string, got {type_name(expression)}.')
if not expression:
raise ValueError('Expression cannot be empty.')
return _evaluate(expression, variable_store, modules, namespace)
except Exception:
raise DataError(f"Evaluating expression '{expression}' failed: "
f"{get_error_message()}")
_evaluate()という関数を実行していることが分かります。
_evaluate()の中身を見てみます。
def _evaluate(expression, variable_store, modules=None, namespace=None):
if '$' in expression:
expression = _decorate_variables(expression, variable_store)
namespace = dict(namespace) if namespace else {}
if modules:
namespace.update(_import_modules(modules))
local_ns = EvaluationNamespace(variable_store, namespace)
return eval(expression, namespace, local_ns)
色々な処理をしていますが、最終的にはpythonのeval()という関数を使って式の評価を実施しています。
つまり、結局、RobotFrameworkのAssertion Keywordというのは、Python側の関数や式評価を使用して条件を評価しており、その結果によってErrorをraiseしているといったメカニズムになります。
RobotFrameworkにはError検知機能があります。
このError検知機能が、python側でraiseされたErrorをcatchして、テストをFAILさせています。
Assertionがない場合は、Errorが発生しなければ、そのままテストはPASSします。
つまり、RobotFrameworkのAssertionとは、pythonでErrorと判定された場合にError Objectをraiseする仕組みを指します。
そして、RobotFrameworkのError検知機能によってpythonのErrorを検知しています。
Assertion機能とError検知機能の組み合わせによって、テストの合否が決定していると言えます。
例えば、下記の図のような試験があるとします。
装置AのGE1にIPアドレスを設定後、IFがUPになった事を確認して、対向の装置BからPingを実行して成功することを確認する試験になります。
1. Assertionが適切でない場合
2. Assertionが適切な場合
Assertionを実行する方法は、下記の2パターンあります。
1. RobotFrameworkのキーワードでAssertionしてErrorをraiseさせる
2. Python Driver作成時に式評価などでAssertionしてErrorをraiseさせる
AssertionをRobotFramework側で実装するのか、Python Driver側で実装するのかはPJ内で議論する必要があります。
筆者が思うに、AssertionをPython側かRobotFramework側で書くかの判断は、試験内容にもよるので、正解はありません。
参考までに、それぞれのメリット/デメリットを紹介します。
Python Driver側では情報の取得・データのparseの処理を実装します。
@keyword
def get_ip_address(self):
# IFの情報を確認
result_list = self.connection.send_command(command_string="show ip interface brief", use_textfsm=True)
logger.write(result_list)
return result_list
RobotFramework側で想定値であることを確認します。
${results} Get Ip Address
FOR ${result} IN ${results}
IF ${result}["interface"] == Gigabitethernet1
Should Be True ${result}["ip_address"] UP
END
END
Python Driver側では情報の取得・想定値のAssertionまで実装します。
@keyword
def check_ip_address(self,interface,ip_address):
# IFの情報を確認
result_list = self.connection.send_command(command_string="show ip interface brief", use_textfsm=True)
logger.write(result_list)
for result in result_list:
if result["interface"] == interface:
if result["ip_address"] == ip_address:
return True
else:
return False
RobotFramework側では実行するだけです。
Check Ip Address Gigabitethernet1 192.168.10.1
上記で、Assertionとは特定の結果が想定された結果であることを確認していると紹介しました。
想定された結果とは、必ずしも正常値だけではなく、異常値の場合もあります。
その場合、Errorをcatchして、想定のErrorの場合、それをTrueとするキーワードもあります。
例えば、Run Keyword And Expect Errorというキーワードがあります。
期待通りのErrorが起きた場合、発生したErrorのError messageを返し、必要ならばそのまま処理やテストを継続できます。
Errorが発生しない時や期待通りのErrorでなかった場合は失敗します。
例えば、下記のような異常系の試験をする時に使用します。
下記の試験では、 Should Be True ${num2} == 10 の実行後、 '5 == 10' should be true. というErrorが得られる事を確認しているような異常系のErrorになります。
*** Variables ***
${num2} 5
*** Test Cases ***
test3
Run Keyword And Expect Error '5 == 10' should be true. Should Be True ${num2} == 10
実行結果は下記のようになります。
Errorが想定の結果であったため、テスト自体はPASSします。
今回は、RobotFrameworkにおけるAssertionについてご紹介しました。
普段からRobotFrameworkでシナリオを書かれている方にとっては、既知の内容かなと思います。
しかし、私自身も、Assertionの必要性、Assertion Keywordが何をしているのか、どうやってRobotFrameworkのPASS/FAILは判断されるのかなど正しく理解できてない部分もあったので、改めて勉強になりました。この記事で皆様の知識の整理にも役立てていただければ幸いです。
また、RobotFrameworkとPython DriverのどちらでAssertionを実装するかについても、PJ内で認識を合わせる際に参考にしてみてください。