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

BDD (動作駆動開発) テストとは何ですか?
BDD (動作駆動型開発) テスト BDDはアジャイルソフトウェア開発の手法の一つであり、TDD(テスト駆動開発)の拡張版です。BDDでは、テストケースはプログラマー以外の人でも読める自然言語で記述されます。
BDD テストはどのように機能するのでしょうか?
あなたはネットバンキングアプリケーションに資金振替モジュールを作成するよう割り当てられたとします。
それを試す方法は複数あります。
- 送金は、送金元口座に十分な残高がある場合に実行されます。
- 送金先の口座情報が正しい場合、送金は行われるはずです。
- ユーザーが入力した取引パスワード/RSAコード/セキュリティ認証が正しい場合、資金送金が実行されます。
- 銀行休業日であっても資金移動は行うべきである
- 資金移動は、口座名義人が設定した将来の日付に行われる必要があります。
その テストシナリオ 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形式のテストレポート
個別のシナリオ結果を表示するテストレポート
動作するコードスイートは、APIが拡大しても読みやすさを維持できる場合にのみ有用であり、そこでフィーチャーファイル管理の規律が効果を発揮する。
Behaveフィーチャーファイルを作成するためのベストプラクティス
Behave スイートは、シナリオが動作ではなくクリックとペイロードを記述している場合、すぐに劣化します。ping Python エンジニアが保守しやすいレイヤー。
- 実装方法ではなく、動作について説明してください。 「顧客の残高が十分である場合」と記述してください。「残高欄が500である場合」とは記述しないでください。フィーチャーファイルにはシステムの動作が記述され、ステップファイルにはそのチェック方法が記述されます。
- シナリオごとに1つの行動を維持する。 ステータスコード、レスポンスボディ、データベース行を主張するシナリオは、実際には3つのシナリオに分けられます。これらを分割することで、障害発生時に原因を直接特定できるようになります。
- 共有設定をバックグラウンドに移動します。 上記の例では、
Background: Given I set sample REST API urlすべてのシナリオがベースを継承するように URL それを繰り返さずに。 - 重複させるのではなく、パラメータ化する。 補強されたプレースホルダーなど
"{request_name}"1つのステップ関数でGET、PUT、DELETEのアサーションを処理するようにすることで、この例ではシナリオよりもはるかに少ないステップ定義で済むようになります。 - データ変動にはシナリオアウトラインを使用してください。 複数の入力に対して同じ動作を証明する必要がある場合、
Examples:表の方が、コピーしたシナリオよりも分かりやすい。 - シナリオ間の依存関係を避ける。 Behaveは、すべての構成においてPOSTシナリオがGETシナリオより先に実行されることを保証するものではありません。各シナリオは、それぞれに必要な状態を作成する必要があります。
- モジュールレベルのグローバル変数をコンテキスト変数に置き換えます。 この例では、簡潔にするためにエンドポイントとレスポンスをモジュール辞書に格納しています。本番環境のスイートでは、Behave の
contextオブジェクトにより、シナリオ間で状態が適切にリセットされます。 - 選択実行のためのタグシナリオ。 タグなど
@smokeor@regression許すbehave --tags=@smokeこれにより、パイプラインのフィードバックが迅速に維持されます。
最初のフィーチャーファイルからこれらの習慣を適用することで、BDDスイートは最初のCRUDの例を超えても価値を保ちます。このアプローチをさらに拡張するチームは、多くの場合、以下と組み合わせて使用します。 Cucumber JVMプロジェクトまたは Postman 探索的なAPIチェック用。






