ブログ|シッテク(CTC情報通信第1本部のメディア)

RobotFrameworkにおけるAssertionについて考えよう

作成者: 大東 沙代|2023.12.11

→資料ダウンロード:大規模ネットワーク自動化成功への手引書

ご挨拶

ネットワークテクノロジー課の大東です。  
新卒でNW自動化開発チームに配属され、2年弱NW自動化開発業務中心に担当しています。

私事なのですが、最近この仕事をしていて良かったなと感じたエピソードがあります。  

とある案件で、1ヶ月で何百台もの機器に対し出荷前の正常性動作確認を行う作業の自動化に携わりました。  
出荷検査を目の前で見たことがなかったのですが、実際に自動化ツールを使った作業風景を見ると、納期が決められている中、作業スペースで1人が1日に何十台もの機器に対して同じ検査を繰り返していました。
あ〜これを手動でやろうとすると、作業者の気力・体力がすり減り、ミスも増えるだろうなと想像するだけで気が滅入りました。  
それと同時に、自動化することで、RobotFrameworkが、試験を担保してくれるので、作業者の負担を軽減できていることや品質の向上に繋がっていると実感し、自動化ツールを作成して良かったと改めて感じました。

そして、この品質を高めるために重要なのが、Assertionだと筆者は思います。  
試験の中身は、Assertionの集合であって、正しくAssertionされていることが重要になります。  
また、どの段階でどうAssertionするかによって、コーディングミスを軽減したり、コード可読性を向上させることもできます。  
今回は、そもそもAssertionってなんだろう?という方からでも理解できるように  「RobotFrameworkにおけるAssertion」についてご紹介させていただきます。

Assertionについて

Assertion(アサーション)とは、プログラムにおいて、あるコードが実行されている時に満たされるべき条件を記述し、実行時にチェックする仕組みを指します。  
つまり、Assertionとは試験の合否判定です。  
特定の結果が想定された結果であることを確認して、テストが妥当であることを確認します。  
Assertionの結果、Assertionが失敗した場合は、Errorや例外を発生させて処理を中断させることができます。  
適切にAssertion(確認ポイント)を実行する事で、そのテストの妥当性が保証されます。

RobotFrameworkにおけるAssertion機能について

RobotFrameworkにはAssertion機能があります。  
対象の値が、指定した条件を満たしているかを判定することが可能です。  
対象の値が指定した条件と合致するかを評価して、Trueの場合はtestcaseがPASSし、Falseの場合はErrorをraiseしてtestcaseがFAILするといった挙動になります。  

Assertion Keyword

RobotFrameworkはAssertion機能として、Built-inのAssertion Keywordを提供しています。  
例えば、 Should Be True  Should Be False Should Be Equal Should Not Be Equal などがあります。  

キーワード名に Should が入っているものは、基本的には、Assertionをするキーワードになると覚えておくと良いでしょう。  
自作ライブラリを作成する時に、この命名規則に倣って関数名をつけるとコードの可読性が向上します。  

Should Be Trueとは

引数が真の時成功し、偽の時に失敗するキーワードになります。  
引数には、 条件式 か 文字列でない要素( listdict など)を指定できます。  
条件式の場合はPythonの式として評価されます。  
意外と、 Should Be True の後に式の評価ができること を知らない人が多いので覚えておくと良いでしょう。  
式の評価とは、下記の例でいうと、 ${num} < 10 のようなPythonの条件式を評価することを指します。  
文字列でない要素の場合は、Pythonにおける真偽値から直接判定します。  

#条件式の場合
Should Be True  ${num} < 10 

#文字列でない場合
Should Be True  ${list}

Assertionの書き方

例えば、変数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になります。

Assertion Keywordは何をしているか

ここからは、`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しているといったメカニズムになります。  

Assertion機能とError検知機能

RobotFrameworkにはError検知機能があります。  
このError検知機能が、python側でraiseされたErrorをcatchして、テストをFAILさせています。  
Assertionがない場合は、Errorが発生しなければ、そのままテストはPASSします。

つまり、RobotFrameworkのAssertionとは、pythonでErrorと判定された場合にError Objectをraiseする仕組みを指します。    
そして、RobotFrameworkのError検知機能によってpythonのErrorを検知しています。
Assertion機能とError検知機能の組み合わせによって、テストの合否が決定していると言えます。 

Assertionの適切なタイミング

例えば、下記の図のような試験があるとします。  
装置AのGE1にIPアドレスを設定後、IFがUPになった事を確認して、対向の装置BからPingを実行して成功することを確認する試験になります。

1. Assertionが適切でない場合  

  • 最終的にPingは成功していますが、もしかしたら、最初からPingが成功していた可能性もあります。  
  • この確認観点だけでは、試験の過程で正常な動作をしていたかどうかが判断できず、試験として品質が低いです。  
  • 仮に、Pingの確認が失敗した場合、どの観点まで正常な動作だったのか判断がつかず、バグの発見・修正に時間がかかります。

2. Assertionが適切な場合  

  •  成功時にAssertionを実施した時点での動作の担保がとれているので、試験の過程で正常な動作をしていることが観点ごとに細かく保証されており、試験として品質が高いです。
  •  仮に、試験に失敗した場合は、観点ごとに正常であることを確認しているので、問題が起こった箇所を効率よく発見できます。



→資料ダウンロード:大規模ネットワーク自動化成功への手引書

Python側かRobotFramework側どちらでAssertionするべきか

Assertionを実行する方法は、下記の2パターンあります。

    1. RobotFrameworkのキーワードでAssertionしてErrorをraiseさせる
    2. Python Driver作成時に式評価などでAssertionしてErrorをraiseさせる

AssertionをRobotFramework側で実装するのか、Python Driver側で実装するのかはPJ内で議論する必要があります。  
筆者が思うに、AssertionをPython側かRobotFramework側で書くかの判断は、試験内容にもよるので、正解はありません。  
参考までに、それぞれのメリット/デメリットを紹介します。  

RobotFramework側でAssertion

具体例

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

メリット

  • できるだけ、Assertion Keywordを利用することで無駄な関数の実装を防げます。

デメリット

  • Robotシナリオ上に複雑なAssertionが全面に出てきてしまっており、コードの可読性が下がるので、コードを読むコストがかかります。
  • RobotFrameworkの1つのメリットである自然言語化することで誰でもキーワードを並べるだけでシナリオが書けるという1つのメリットが失われます。
  • 処理を前面に出すことでRobotFrameworkでシナリオを書くハードルが高くなってしまいます。
  • RobotFrameworkのFOR/IF文が単純に書きづらいです。
  • FORの間は2マス空白を開け、条件式の要素の間は1マス空白をあける、末尾にはENDを入れるなど気を付けるべき点が多いので、書きづらいです。

Python側でAssertion

具体例

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を自分で定義でき、Pythonで書くことでより柔軟なAssertion処理を実装できます。  
  • キーワード名から処理を大体予測できるので、Robotシナリオの可読性が向上します。
  • オペレーターがAssertionの処理内容まで気にせずにテスト内容を把握し実行できます。また、実行logに細かいAssertion内容が残らないので、実行logが見やすいです。

デメリット

  • あまり複雑でない処理の実装は、Should Be Trueなどのキーワードを使えば良いので、無駄な実装になります。

異常系のAssertionについて

上記で、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内で認識を合わせる際に参考にしてみてください。