APRESIA Technical Blog

behaveにて条件分岐を考慮したテスト自動化のテストケースとスクリプトを書いてみた

はじめに

BDDによる検証テスト自動化について、JANOG41, JANOG45にて発表してきました。BDDはBehavior Driven Developmentの略称です。Webの受入テストでよく使われてきたフレームワークで、テストケースと具体的な実装を分けることが可能です。BDDで記述されたテストケースは、基本的に先頭行から逐次実行しますが、検証自動化のレベルでは十分でした。しかし、今後、B5G時代に向けて運用自動化を見据えた場合、条件判定処理とその後続の処理の自動化も必要になってきます。たとえば、ファームウェアの更新失敗時に何か特別な処理をしたい場合です。これまででもPythonスクリプトに全て記述すれば可能でしたが、テストケース上での記述は難しく汎用性が低くなることがわかっていました。そこで、今回は、behave1.2.6に備わっている機能を利用しつつ、条件分岐先をテストケースから指定できるサンプルを作成してみました。

BDD (Behavior Driven Development) のイメージ

テストケースと具体的な実装を分けるための仕組み

Webの受け入れテストで、よく使われるフレームワーク

behaveとは

PythonベースのBDDフレームワークかつ実行環境です。

テストケース記述は以下の3つのファイルを作成します。

  • Featureファイル
    Gherkin書式にてテストケースを記述
  • Stepファイル
    個別の動作をPythonにて記述
  • environment.py
    テスト実行中の特定のイベントの前後に実行するコードを記述

代表的なファイル配置は下記となります。

+--- jump.feature          // Feature ファイル
+--- environment.py        // environment.py
+--- steps
     +--- jump.py          // Stepファイル

テスト失敗時の条件分岐に実装方針ついて

behave のNoteworthy in Version 1.2.6 にて、Runner: Can continue after a failed step in a scenario の追加が記載されています。この機能によって、あるテストシナリオが失敗した後もテストを継続実行できます。この機能をもとに、behave本体には変更を加えず、あるテストシナリオが失敗したときに、実施したいテストシナリオにまでスキップするテストスクリプトを作成してみました。

実行したbehaveversion

リリースされている最新版の1.2.6を使用

root@b9512447bdf2:~/python/sample# behave --version
behave 1.2.6

Featureファイル

Featureファイルに記述Step文の文法

<Given|When|Then> <Step文> @If <条件> Next <飛び先>

  • @If Failedではステップの失敗時に飛び先にまでスキップします。
  • @If Passedではステップの成功時に飛び先にまでスキップします。
  • Next stepでは同じシナリオ内の最初の別のstepにまでスキップします。
    Stepには名前を付けられない(=indexでしか参照できない)ので、暫定的にステップ種別を指定することにします。
  • Next scenarioでは別のシナリオにまでスキップします。
<Given|When|Then> <Step文> @If <Failed|Passed> Next step <Given|When|Then>
<Given|When|Then> <Step文> @If <Failed|Passed> Next scenario <シナリオ名>

 

今回のサンプルでは、条件はFailedのみ実装します。Step文はevent sleep {sec}とします。

  • シナリオ内条件分岐
    Scenario Bの先頭行のGivenでテストを失敗させて、4行目のGivenまでスキップします。つまり、2行目のWhenと3行目のThenをスキップします。
  • シナリオ間条件分岐
    Scenario B の4行目のGivenでテストを失敗させて、Scenario D までスキップします。つまり、直後のWhenとScenario Cをスキップします。

Stepファイル

@step(‘event sleep {sec}‘)にて定義されるステップ関数にて、下記処理を実施する。

  • スキップ条件とスキップ先をcontext.conditionに格納
    set_condition関数にて、文字列を@前後で分割し、@以降がある場合、スペースにてsplitし、その結果をcontext.conditionに保存
  • sleepを実行

context.conditionのプロパティ一覧

# -*- coding: utf-8 -*-

from __future__ import unicode_literals
from __future__ import print_function
from behave import *
#from hamcrest import *
import time

@step('event sleep {sec}')
def step_impl(context, sec):
    sec = set_condition(context,sec)
    time.sleep(int(sec))

def set_condition(context, str):
    split = str.split('@')

    if len(split) > 1:
        condition = split[1].split(' ‘)
        context.condition['cond'] = condition[1]
        context.condition['kind'] = condition[3]
        context.condition['to'] = condition[4]
        context.condition['enable'] = True

    return split[0]

environment.py

フック関数:before_all()

  • 各種初期化を実施します。

フック関数:before_scenario()

  • シナリオに入る前にcontextに保存したスキップ条件をチェックし、条件を満たすまでScenarioクラスの関数であるskip()でスキップします。
  • 指定したスキップ先に到達した後は、context.conditionに保存した条件を削除します。

フック関数:before_step()

  • ステップを実行する前にcontextに保存したスキップ条件をチェックし、条件を満たすまでstatusをskippedに設定します。
  • 指定したスキップ先に到達した後は、context.conditionに保存した条件を削除します。

注意事項

  • スキップするとテストのステータスはskippedになりますが、behaveでは本体に修正を加えない場合、この方法以外にシナリオの実行をキャンセルする方法は存在しなさそうで、明確に「ジャンプ」させることはできないようです。
root@b9512447bdf2:~/python/sample# cat environment.py
# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from behave.model_core import Status
from behave.model import Scenario

import sys

def before_all(context):
    sys.tracebacklimit = -1
    userdata = context.config.userdata
    continue_after_failed = userdata.getbool('runner.continue_after_failed_step', False)
    Scenario.continue_after_failed_step = continue_after_failed

    context.condition = {
        'enable': False,
        'cond': None,
        'kind': None,
        'to': None
  }


def before_scenario(context, scenario):
    if context.condition['enable']:
        if context.condition['kind'] == 'scenario':
            if context.scenario.name != context.condition['to']:
                context.scenario.skip()
            else:
                context.condition['enable'] = False
                context.condition['cond'] = False
                context.condition['kind'] = None
                context.condition['to'] = None

def before_step(context, step):
    if context.condition['enable']:
        if context.condition['kind'] == 'scenario':
            step.status = Status.skipped
        elif context.condition['kind'] == 'step':
            if step.keyword != context.condition['to']:
                step.status = Status.skipped
            else:
                context.condition['enable'] = False
                context.condition['cond'] = False
                context.condition['kind'] = None
                context.condition['to'] = None

 

サンプルテストの実行結果

実行結果の解説

  • コマンドライン引数にcontinue_after_failed_stepを渡しています。
  • 文字色ですが、緑が成功、赤字が失敗、水色がスキップされたテストです。
  • シナリオ内条件分岐(スキップ)
    Scenario Bの1行目のGivenにてsleepイベントに負数(-1)を渡すことによってValueErrorが発生し、テストが失敗します。
    このとき、If以後に記されているとおり、3行後のGivenまでスキップします。
  • シナリオ間条件分岐(スキップ)
    Scenario Bの4行目のGivenがValueErrorを起こして失敗します。このときIf以後に記述されているとおり、scenario Dにまでスキップします。
  • Failure scenariosとして、Scenario Bが選択されています。

最後に

以上、behave本体には手を加えないで条件分岐を実行する方法を紹介いたしました。繰り返し実行については、behave本体の修正が必要となるため今回は割愛しております。今回ご紹介した方法にて、テストケース上からも条件分岐を指定することができるようになり、複雑なテストをより汎用的な文法で記述することが可能となります。APRESIA テスト自動化システムのご紹介で紹介しましたAPRESIAテスト自動化システムツール Excel2Feature(E2F)との連携等、お問い合わせに尽きましては以下からご連絡ください。