BDDテストとは? ビヘイビア駆動開発フレームワーク

⚡ スマートサマリー

BDDテストは、アプリケーションの動作を平易なGiven-When-Then言語で記述し、アナリスト、開発者、テスターが共通の仕様を共有できるようにします。このチュートリアルでは、Behaveを使用したREST APIテストにそのアプローチを適用します。 Python セットアップ、機能ファイル、ステップの実装、実行、レポート作成を網羅するフレームワーク。

  • 🗣️ 基本原則: ビヘイビア駆動開発は、テスト駆動開発を拡張したもので、すべてのシナリオをプログラマー以外の人でも理解し承認できる自然言語で表現します。
  • 🧱 シナリオ構成: Given は前提条件を設定し、When はアクションを実行し、Then は結果を表明します。一方、And または But は前のステップのデコレータを継承します。
  • 🌐 RESTの適用範囲: この例では、公開されている jsonplaceholder posts サービスに対して、POST、GET、PUT、DELETE の操作を通じて、完全な CRUD 動作を実行します。
  • 🐍 フレームワークの設定: Behaveはpip経由でインストールされます Python 3 機能ファイルを含む features ディレクトリと、実装を含む steps パッケージが必要です。
  • 🔗 ステップバインディング: @given、@when、@thenなどのデコレータはフィーチャーテキストに一致し、中括弧で囲まれたプレースホルダーはシナリオから値を渡す Python 機能。
  • 実行: 見やすいフォーマッタは、各ステップの合否結果を色分けしてコンソールに表示します。
  • 📊 レポート: allure-behaveフォーマッタは、機械可読な結果を出力し、Allureコマンドラインはそれを閲覧可能なHTMLレポートとしてレンダリングします。

BDDテストとは

BDD (動作駆動開発) テストとは何ですか?

BDD (動作駆動型開発) テスト BDDはアジャイルソフトウェア開発の手法の一つであり、TDD(テスト駆動開発)の拡張版です。BDDでは、テストケースはプログラマー以外の人でも読める自然言語で記述されます。

BDD テストはどのように機能するのでしょうか?

あなたはネットバンキングアプリケーションに資金振替モジュールを作成するよう割り当てられたとします。

それを試す方法は複数あります。

  1. 送金は、送金元口座に十分な残高がある場合に実行されます。
  2. 送金先の口座情報が正しい場合、送金は行われるはずです。
  3. ユーザーが入力した取引パスワード/RSAコード/セキュリティ認証が正しい場合、資金送金が実行されます。
  4. 銀行休業日であっても資金移動は行うべきである
  5. 資金移動は、口座名義人が設定した将来の日付に行われる必要があります。

その テストシナリオ Y日間またはYヶ月の期間にわたって金額Xを送金するなどの追加機能を考慮すると、より精巧で複雑になります。ping 合計金額がZに達した時点での定期送金、といった具合です。

開発者の一般的な傾向としては、まず機能を開発し、テストコードは後から書くというものです。上記の例からも明らかなように、 テストケース ここでの開発は複雑なので、開発者は延期します テスト 発売まではテストは行われず、発売時点で迅速だが効果のないテストが実施される。

この問題を克服するために、ビヘイビア駆動開発(BDD)が考案されました。BDDは、開発者にとってテストプロセス全体をより簡単にします。

BDD では、書き込んだものはすべて 与えられたとき、その時 ステップ。上記の例をBDDで考えてみましょう。

Given that a fund transfer module in net banking application has been developed
And I am accessing it with proper authentication

When I shall transfer with enough balance in my source account
Or I shall transfer on a Bank Holiday
Or I shall transfer on a future date
And destination a/c details are correct
And transaction password / rsa code / security authentication for the transaction is correct
And press or click send button

Then amount must be transferred
And the event will be logged in log file

このフォームは、書きやすく、読みやすく、理解しやすい。資金振替モジュールのあらゆるテストケースを網羅しており、さらに迅速に修正して追加にも対応できる。また、モジュールの生きたドキュメントのように読める。BDDはTDDから発展したため、ツール開発に進む前に両者の違いを明確にしておく価値がある。

BDDとTDD:主な違い

どちらの手法もテストファーストであり、フィードバックループを短縮する。両者の違いは、テストを作成する人、テストで使用する言語、そしてアプリケーションのどの層を対象とするかという点にある。

側面 TDD(テスト駆動開発) BDD (行動駆動型開発)
フォーカス コードの単位が内部的にどのように動作するか ユーザーの視点から見たシステムの動作
言語 プログラミング言語のアサーション 自然言語によるGiven-When-Thenシナリオ
筆頭著者 Developer ビジネスアナリスト、プロダクトオーナー、テスター、開発者が一体となって
Readable プログラマー以外の人による いいえ はい
典型的なスコープ ユニットレベル 機能、API、および受け入れレベル
一般的なツール JUnit、pytest、NUnit 振る舞う、 CucumberSpecFlow、JBehave

この2つは競合するのではなく、補完し合う関係にある。チームは一般的に TDD ユニットレベルの設計には、BDDシナリオを追加して、ステークホルダーが実際に承認する動作を記述します。RESTエンドポイントはまさにその承認レイヤーに位置するため、BDDとの相性は抜群です。

REST APIテストとは何ですか?

RESTがAPI構築の一般的なスタイルとなったことで、UIテストケースと並行してREST APIテストケースを自動化することが同様に重要になってきている。 APIテスト これには、POST、GET、PUT、DELETE メソッドをそれぞれ用いて、CRUD(作成-読み取り-更新-削除)アクションをテストすることが含まれます。

振る舞いとは何ですか?

Behaveは人気の Python BDDテストフレームワーク。Behaveの機能は以下のとおりです。

  • フィーチャーファイルは、ビジネスアナリスト、スポンサー、または動作シナリオの所有者によって作成されます。フィーチャーファイルは、機能または機能の一部を、期待される結果の代表的な例とともに、自然言語形式で記述します。
  • これらのシナリオ手順は、 Python.
  • オプションとして、環境制御機能は、ステップ、シナリオ、機能、または実行全体に対して、前後でコードを実行します。

各ファイルの役割が明確になったので、フレームワークをインストールできます。

BDD テスト フレームワークのセットアップ Windows

インストール:

  • ダウンロードしてインストール Python から3 https://www.python.org/
  • Behaveをインストールするには、コマンドプロンプトで次のコマンドを実行してください。
  • pip install behave
  • IDE: PyCharm ここではコミュニティ版を使用しています。 https://www.jetbrains.com/pycharm/download/

プロジェクトの設定:

  • 新しいプロジェクトを作成します
  • 以下のディレクトリ構造を作成します。

プロジェクトの設定

上記のスクリーンショットは、Behaveが想定するレイアウトを示しています。 機能を使用 フィーチャー ファイルを格納するディレクトリ、およびネストされた ステップ ディレクトリには Python 実装。Behaveは両方を名前で検出するため、フォルダ名は完全に一致する必要があります。

機能ファイル:

それではフィーチャーファイルを作成しましょう Sample_REST_API_Testing.feature主な機能は、「posts」サービスに対するCRUD操作です。

この例では、 https://jsonplaceholder.typicode.com/ サンプルRESTサービスを掲載します。これは、書き込みリクエストを受け付け、変更を永続化することなく現実的なレスポンスを返す、無料の模擬APIです。

POST シナリオの例

Scenario: POST post example -> creating a new post item using the 'posts' service
Given I set post posts API endpoint          -> prerequisite: sets the URL of the posts service
When I set HEADER param request content type as "application/json"
And set request body
And send POST HTTP request                    -> the actual test step
Then I receive valid HTTP response code 201
And Response body "POST" is non-empty       -> verification of the response body

同様に、残りのシナリオも次のように記述できます。

プロジェクトの設定

Sample_REST_API_Testing.feature

Feature: Test CRUD methods in Sample REST API testing framework

Background:
    Given I set sample REST API url

Scenario: POST post example
    Given I Set POST posts api endpoint
    When I Set HEADER param request content type as "application/json"
    And Set request Body
    And Send a POST HTTP request
    Then I receive valid HTTP response code 201
    And Response BODY "POST" is non-empty

Scenario: GET posts example
    Given I Set GET posts api endpoint "1"
    When I Set HEADER param request content type as "application/json"
    And Send GET HTTP request
    Then I receive valid HTTP response code 200 for "GET"
    And Response BODY "GET" is non-empty

Scenario: UPDATE posts example
    Given I Set PUT posts api endpoint for "1"
    When I Set Update request Body
    And Send PUT HTTP request
    Then I receive valid HTTP response code 200 for "PUT"
    And Response BODY "PUT" is non-empty

Scenario: DELETE posts example
    Given I Set DELETE posts api endpoint for "1"
    When I Send DELETE HTTP request
    Then I receive valid HTTP response code 200 for "DELETE"

ステップの実装

さて、上記のシナリオで使用される機能ステップについては、実装を記述できます。 Python 「steps」ディレクトリ内のファイル。

Behaveフレームワークは、デコレータをフィーチャーファイル述語と照合することでステップ関数を識別します。たとえば、 与えられた フィーチャーファイルシナリオの述語は、ステップ関数を検索します。 @given デコレータ。同じマッチングが次の場合にも発生します。    (NAIST) と その後「But」と「And」の場合、ステップ関数は前のステップと同じデコレータを受け取ります。たとえば、「And」が「Given」の後に続く場合、対応するステップ関数デコレータは次のようになります。 @given.

例えば、POST の When ステップは次のように実装できます。 "application/json" フィーチャーファイルから中括弧で囲まれたプレースホルダーに渡されます。これはパラメータ化と呼ばれます。

# Decorator: the braced name captures a value from the feature file
@when(u'I Set HEADER param request content type as "{header_content_type}"')
def step_impl(context, header_content_type):
    # Step implementation: set the content type on the request header
    request_headers['Content-Type'] = header_content_type

同様に、ステップ内の他のステップの実装 Python ファイルは次のようになります。

ステップの実装

サンプル_ステップ_実装.py

from behave import given, when, then, step
import requests

api_endpoints = {}
request_headers = {}
response_codes = {}
response_texts = {}
request_bodies = {}
api_url = None

@given(u'I set sample REST API url')
def step_impl(context):
    global api_url
    api_url = 'https://jsonplaceholder.typicode.com'

# START POST Scenario
@given(u'I Set POST posts api endpoint')
def step_impl(context):
    api_endpoints['POST_URL'] = api_url + '/posts'
    print('url :' + api_endpoints['POST_URL'])

@when(u'I Set HEADER param request content type as "{header_content_type}"')
def step_impl(context, header_content_type):
    request_headers['Content-Type'] = header_content_type

# "And" or "But" steps are renamed by behave to match their preceding step
@when(u'Set request Body')
def step_impl(context):
    request_bodies['POST'] = {"title": "foo", "body": "bar", "userId": "1"}

@when(u'Send POST HTTP request')
def step_impl(context):
    # send the request and save the response object
    response = requests.post(url=api_endpoints['POST_URL'],
                             json=request_bodies['POST'],
                             headers=request_headers)
    response_texts['POST'] = response.text
    print("post response :" + response.text)
    response_codes['POST'] = response.status_code

@then(u'I receive valid HTTP response code 201')
def step_impl(context):
    print('Post rep code ;' + str(response_codes['POST']))
    assert response_codes['POST'] == 201
# END POST Scenario

# START GET Scenario
@given(u'I Set GET posts api endpoint "{id}"')
def step_impl(context, id):
    api_endpoints['GET_URL'] = api_url + '/posts/' + id
    print('url :' + api_endpoints['GET_URL'])

@when(u'Send GET HTTP request')
def step_impl(context):
    response = requests.get(url=api_endpoints['GET_URL'], headers=request_headers)
    response_texts['GET'] = response.text
    response_codes['GET'] = response.status_code

@then(u'I receive valid HTTP response code 200 for "{request_name}"')
def step_impl(context, request_name):
    print('Get rep code for ' + request_name + ':' + str(response_codes[request_name]))
    assert response_codes[request_name] == 200

@then(u'Response BODY "{request_name}" is non-empty')
def step_impl(context, request_name):
    print('request_name: ' + request_name)
    print(response_texts)
    assert response_texts[request_name] is not None
# END GET Scenario

# START PUT/UPDATE
@given(u'I Set PUT posts api endpoint for "{id}"')
def step_impl(context, id):
    api_endpoints['PUT_URL'] = api_url + '/posts/' + id
    print('url :' + api_endpoints['PUT_URL'])

@when(u'I Set Update request Body')
def step_impl(context):
    request_bodies['PUT'] = {"title": "foo", "body": "bar",
                             "userId": "1", "id": "1"}

@when(u'Send PUT HTTP request')
def step_impl(context):
    response = requests.put(url=api_endpoints['PUT_URL'],
                            json=request_bodies['PUT'],
                            headers=request_headers)
    response_texts['PUT'] = response.text
    print("update response :" + response.text)
    response_codes['PUT'] = response.status_code
# END PUT/UPDATE

# START DELETE
@given(u'I Set DELETE posts api endpoint for "{id}"')
def step_impl(context, id):
    api_endpoints['DELETE_URL'] = api_url + '/posts/' + id
    print('url :' + api_endpoints['DELETE_URL'])

@when(u'I Send DELETE HTTP request')
def step_impl(context):
    response = requests.delete(url=api_endpoints['DELETE_URL'])
    response_texts['DELETE'] = response.text
    print("DELETE response :" + response.text)
    response_codes['DELETE'] = response.status_code
# END DELETE

⚠️注意: ステータスコードを比較して == ではなく is 問題。 is 演算子は等価性ではなくオブジェクトの同一性をテストし、 Python 3.8以降は SyntaxWarning: “is” とリテラル そのパターンに対して。 assert code is 201 次のように書き換えるべきです assert code == 201.

テストの実行

テストスクリプトの開発が完了したので、テストを実行しましょう。コマンドプロンプトで以下のコマンドを実行して、フィーチャーファイルを実行します。

:: Run a single feature file with the pretty formatter
behave -f pretty features\feature_files_folder\Sample_REST_API_Testing.feature

:: Run every feature in the project
behave

テスト実行結果は以下のように表示されます。

テストの実行

コンソールへのレポート表示

コンソール出力は開発中は便利ですが、関係者は通常、読みやすいレポートを好みます。Allureはそのようなレポートを提供します。

レポート

まず、Allure BehaveフォーマッタとAllureコマンドラインツールをインストールします。フォーマッタは Python パッケージ。コマンドラインツールは、別途ダウンロードして説明されています。 Allure Reportのドキュメント.

:: 1. Install the formatter
pip install allure-behave

:: 2. Run the tests and write raw results to a folder
behave -f allure_behave.formatter:AllureFormatter -o reports/allure-results features/

:: 3. Render and open the HTML report
allure serve reports/allure-results

これにより、以下のような見やすく分かりやすい形式でテスト結果レポートが生成されます。

レポート

HTML形式のテストレポート

HTML形式のテストレポート

個別のシナリオ結果を表示するテストレポート

動作するコードスイートは、APIが拡大しても読みやすさを維持できる場合にのみ有用であり、そこでフィーチャーファイル管理の規律が効果を発揮する。

Behaveフィーチャーファイルを作成するためのベストプラクティス

Behave スイートは、シナリオが動作ではなくクリックとペイロードを記述している場合、すぐに劣化します。ping   Python エンジニアが保守しやすいレイヤー。

  1. 実装方法ではなく、動作について説明してください。 「顧客の残高が十分である場合」と記述してください。「残高欄が500である場合」とは記述しないでください。フィーチャーファイルにはシステムの動作が記述され、ステップファイルにはそのチェック方法が記述されます。
  2. シナリオごとに1つの行動を維持する。 ステータスコード、レスポンスボディ、データベース行を主張するシナリオは、実際には3つのシナリオに分けられます。これらを分割することで、障害発生時に原因を直接特定できるようになります。
  3. 共有設定をバックグラウンドに移動します。 上記の例では、 Background: Given I set sample REST API url すべてのシナリオがベースを継承するように URL それを繰り返さずに。
  4. 重複させるのではなく、パラメータ化する。 補強されたプレースホルダーなど "{request_name}" 1つのステップ関数でGET、PUT、DELETEのアサーションを処理するようにすることで、この例ではシナリオよりもはるかに少ないステップ定義で済むようになります。
  5. データ変動にはシナリオアウトラインを使用してください。 複数の入力に対して同じ動作を証明する必要がある場合、 Examples: 表の方が、コピーしたシナリオよりも分かりやすい。
  6. シナリオ間の依存関係を避ける。 Behaveは、すべての構成においてPOSTシナリオがGETシナリオより先に実行されることを保証するものではありません。各シナリオは、それぞれに必要な状態を作成する必要があります。
  7. モジュールレベルのグローバル変数をコンテキスト変数に置き換えます。 この例では、簡潔にするためにエンドポイントとレスポンスをモジュール辞書に格納しています。本番環境のスイートでは、Behave の context オブジェクトにより、シナリオ間で状態が適切にリセットされます。
  8. 選択実行のためのタグシナリオ。 タグなど @smoke or @regression 許す behave --tags=@smokeこれにより、パイプラインのフィードバックが迅速に維持されます。

最初のフィーチャーファイルからこれらの習慣を適用することで、BDDスイートは最初のCRUDの例を超えても価値を保ちます。このアプローチをさらに拡張するチームは、多くの場合、以下と組み合わせて使用​​します。 Cucumber JVMプロジェクトまたは Postman 探索的なAPIチェック用。

よくあるご質問

どちらもGherkinフィーチャーファイルを読み込む。 Cucumber ステップをバインドします Javaルビー、または Javaスクリプトは、Behaveはそれらをバインドします Python. ご使用のアプリケーションスタックに合ったものを選択してください。そうすることで、ステップ定義で既存のテストユーティリティを再利用できます。

はい。Behaveは、いずれかのシナリオが失敗した場合にゼロ以外の終了コードを返します。 JenkinsGitLab CIやGitHub Actionsなどのツールは、ビルドを自動的に失敗させる可能性があります。レポート作成のために、Allureの結果フォルダをビルド成果物として公開してください。

Yes. AI アシスタントは、ユーザーストーリーまたはOpenAPI仕様からGiven-When-Thenシナリオを作成します。 Rev生成されたシナリオは多くの場合、正常なシナリオのみを網羅しているため、欠落しているネガティブケースやビジネスルールの出力を確認してください。

AIツールは、重複またはほぼ重複するシナリオを検出し、再利用可能なパラメータ化された手順を提案し、不安定な障害を考えられる原因ごとにクラスタリングします。これにより、フィーチャファイルがチーム間で増殖するにつれて、手動による監査作業が軽減されます。

環境ファイル environment.py の before_all フックでトークンを一度取得し、コンテキストオブジェクトに保存した後、各リクエストステップで Authorization ヘッダーとして添付します。認証情報は環境変数に保存し、フィーチャーファイルには絶対に保存しないでください。