BDD テスト記述構文リファレンス

ビヘイビア駆動開発 (BDD) とは、DataWeave での処理用に BAT テストを記述するための構文です。BDD とは、Mocha や Jasmine などの他のテストフレームワークに類似した埋め込みドメイン固有言語 (EDSL) です。

次のステートメントが各 BAT テストに必要です。

import * from bat::BDD

このステートメントはドメイン固有言語 (DSL) のテストライブラリ BDD.dwl をインポートします。このライブラリは、describe、it、GET、POST、sleep など、記述時に頻繁に使用する言葉を定義するものです。

このステートメントは mustEqual、mustMatch、every、oneOf、assert などの一般的なマッチャーをインポートします。

次の例は、一般的なテストの記述開始を示しています。

import * from bat::BDD
import * from bat::Assertions
import * from bat::Mutable


var context = HashMap()
---
describe("the login flow") in [
  it("tries a secured endpoint") in [
    GET `http://127.0.0.1:4567/secured_by_token` with {

    } assert [
      $.response.status mustEqual 401,
    ]
  ],
  it("gets the access token") in [
    POST `http://127.0.0.1:4567/get_access_token` with {} assert [
      $.response.body.new_token mustMatch /accessToken_\d+/,
    ] execute [
      context.set('token', $.response.body.new_token)
    ] assert [
      context.get('token') mustMatch /accessToken_\d+/,
      context.get('token') mustEqual $.response.body.new_token,
      $.response.status mustEqual 200,
    ]
  ],
  it("tries to get a secured endpoint") in [
    GET `http://127.0.0.1:4567/secured_by_token/header` with {
      headers: {
        Authorization: context.get('token')
      }
    } assert [
      $.request.headers.Authorization mustEqual context.get('token'),
      $.response.status mustEqual 200
    ]
  ]
]

テストファイルについて

各機能シナリオについて ​.dwl​ ファイルを作成します。ファイルはフォルダーに基づいて編成することをお勧めします。この編成は、BAT テストが生成するレポートに対応できます。次に例を示します。

tests
├── CoreServices
│   └── Sanity.dwl
└── DesignCenter
    ├── APIDesigner
    │   └── Designer.dwl
    └── MyFolder
        ├── DataSense.dwl
        └── GetProjects.dwl

テストファイルには ​describe​ 式が含まれている必要があります。

import * from bat::BDD
---
describe `Demo scenario` in [

]

テストスイートには 1 つ以上のテスト (.dwl) ファイルが含まれている必要があります。

テストで機密情報を保護する

テストでは、Anypoint シークレットマネージャーから共有シークレットにアクセスできます。シークレットマネージャーには、パスワード、認証トークン、エンドポイント URL、Web フック URL などの機密情報を安全に保存できます。

制限事項

  • 機能監視では、Anypoint シークレット監視の対称キーの共有シークレット種別のみが機能します。

  • テストスイートでは、非公開の場所からそのスイートでテストを実行する場合にのみ共有シークレットの別名を使用できます。

Anypoint シークレットマネージャーでシークレットを設定する

非公開の場所から実行するテストで機密情報を秘密にしておくには、シークレットマネージャーで共有シークレットを作成します。

共有シークレットを設定する手順は、次のとおりです。

  1. シークレットグループを開くか、新規作成します。BAT CLI を使用している環境にグループを作成します。

    BAT CLI を現在使用している環境を確認するには、次の操作を行います。

    1. コマンド ​bat whoami​ を実行します。

      出力には環境の ID があります。

    2. アクセス権のある環境をリストするには、コマンド ​bat environment ls​ を実行します。

    3. bat whoami​ コマンドの ID とリストされた環境の ID を照合します。

    4. シークレットグループが含まれる環境に切り替える必要がある場合、コマンド ​bat environment switch name​ を実行します。​name​ は環境の名前です。

  2. [Shared Secret (共有シークレット)]​ を選択します。

  3. [Type (種別)]​ 項目で ​[Symmetric Key (対称キー)]​ を選択します。

  4. [Key (キー)]​ 項目に、Base64 文字列としてエンコードされた機密情報を貼り付けます。

  5. Base64 文字列を ​[Confirm Key (キーを確認)]​ 項目に貼り付けます。

監視の ​main.dwl​ファイルを変更してシークレット情報をコードに追加したら、シークレットマネージャーで設定したシークレットへのアクセス権を監視に付与します。「​シークレットへのアクセス権を監視に付与する​」を参照してください。

シークレットへのアクセス権を監視に付与する

監視を変更してシークレット情報を追加したら、シークレットへのアクセス権を監視に付与します。BAT CLI は、実行時に別名を使用して共有シークレットを参照します。

シークレットへのアクセス権を監視に付与します。

  1. Anypoint シークレットマネージャーでシークレットを設定する​ で取得した新しい共有シークレットの名前をコピーします。

  2. コマンドプロンプトで、共有シークレットの別名を指定して ​bat grant​ コマンドを実行します。このコマンドを実行すると、BAT CLI はテストスイートの ​bat.yaml​ ファイルに ​secrets​ という名前のセクションを作成します (セクションがまだ存在していない場合)。そのセクションで、BAT CLI は次のインデントされた行を追加します。

    alias:
     secretId: "secret-ID"
    • alias​: bat grant​ コマンドで指定した別名。

    • secret-ID​: Anypoint シークレットマネージャー内のシークレットの ID。この ID は ASM には表示されません。そのため、​bat.yaml​ ファイルを確認して ID から特定のシークレットを連想することはできません。BAT CLI は、この ID を使用して別名に関連付けたシークレットを参照します。

別名の使用例

次の例のように別名を変数に保存して、テストで別名を使用します。

import * from bat::BDD
import * from bat::Assertions

var mySecret: String = secret('myAlias') default 'notFound'

---
suite(parts="Simple Suite") in [
  it must 'answer 200' in [
    GET '$(config.url)?token=$(mySecret)' with {} assert [
      $.response.status mustEqual 200
    ] execute
  ]
]
mySecret​

この例では、シークレットが保存される変数。変数は文字列として宣言されます。

secret('myAlias')​

secret()​ 関数では、入力 ​'name-of-alias'​ が必要です。​name-of-alias​ は、使用する共有シークレットの別名です。

default 'notFound'​

指定された別名の共有シークレットが Anypoint シークレットマネージャーに存在しない場合に表示されるメッセージ。

$(mySecret)​

変数 ​mySecret​ を使用して、設定ファイル (テストスイートを実行する ​bat​ コマンドを入力したときに指定) で定義されている URL のエンドポイントの認証トークンを指定します。

たとえば、設定ファイルが次のようになっているとします。

サンプル設定ファイル dev-environment.dwl
{
  url: "https://dev.yourcompany.com/shoppingCart"
}

この場合、テストスイートの ​bat.yaml​ ファイルがあるディレクトリでコマンドが実行され、デフォルトプロファイルが使用されていると、このテストスイートを実行するコマンドは次のようになります。

bat --config=dev-environment.dwl

BAT CLI は実行時に変数を Anypoint シークレットマネージャーで指定された別名に関連付けられている共有シークレットに置き換えます。

テストブロック

テストブロックにより、テストをより理解しやすくなります。すべてのテストブロックは関数コールまたはカスタム補間値として記述できます。

テストキーワード

次のキーワードおよび次の Fowler の ​GivenWhenThen​ 構造をテストで使用します。

Function Name (関数名) 説明 失敗した場合に実行を停止する

describe​

グループ化用。レポートのコンテキスト情報を追加するために深くネストできます

いいえ

scenario​

describe と同じ

いいえ

suite​

describe と同じ

いいえ

given​

テストステップ。前処理に使用します

はい

when​

テストステップ。通常は副次的影響を作成するために使用します

はい

it​

テストステップ。検証

いいえ

should​

テストステップ

いいえ

must​

テストステップ

はい

it should "str"

テストステップ

いいえ

it must ""

テストステップ

はい

while

テストステップ

いいえ

until

テストステップ

いいえ

テストブロックについて

テストブロックには簡単な名前があり、ブロックが ​x<name>​ として記述されるのをスキップします。たとえば、​describe​ のスキップされたバージョンは ​xdescribe​ です。

describe("A flow")  in [ ... ] // Describes a sequence of test blocks
xdescribe("A flow") in [ ... ] // The same, but skipped.

describe `A flow` in [ ... ]   // The same as describe, but with a custom interpolation
xdescribe `A flow` in [ ... ]  // The same, but skipped.

カスタム補間値について

カスタム補間値により、関数をコールして、補間値を 2 つのリストに分解できます。

  • 最初のリストには、文字列リテラルがすべて含まれます。

  • 2 つ目のリストには、補間された値が含まれます。

値を文字列に変換する必要はありません。カスタム補間のテキストおよび値はバッククォート (`) で囲みます。

次のブロックのすべての項目をカスタム補間値として使用でき、これらには ​x<name>​ バージョンがあります。

import * from bat::BDD         // <-----
import * from bat::Assertions
---
describe `User trades stocks` in [
  scenario `User requests a sell before close of trading` in [
    given `I have 100 shares of MSFT stock` in [
      POST `http://broker/create_stocks` with {
        body: {
          quantity: 100,
          paper: 'MSFT'
        }
      } assert [
        $.response.status == 201
      ]
    ],
    given `I have 150 shares of APPL stock` in [
      POST `http://broker/create_stocks` with {
        body: {
          quantity: 150,
          paper: 'APPL'
        }
      } assert [
        $.response.status == 201
      ]
    ],
    when `I ask to sell 20 shares of MSFT stock` in [
      POST `http://broker/sell_stocks` with {
        body: {
          quantity: 20,
          paper: 'MSFT'
        }
      } assert [
        $.response.status == 201
      ]
    ],
    it should "have 80 shares of MSFT stock" in [
      GET `http://broker/get_stocks/MSFT` with {
        headers: {}
      } assert [
        $.response.status == 200,
        $.response.body.quantity == 80
      ]
    ],
    it should "have 150 shares of APPL stock" in [
      GET `http://broker/get_stocks/APPL` with {
        headers: {}
      } assert [
        $.response.status == 200,
        $.response.body.quantity == 150
      ]
    ]
  ]
]

お使いのコーディングスタイルガイドに準拠するために、カスタム補間値を使用せずにブロックを記述することもできます。これによって動作に影響が及ぶことはありません。

import * from bat::BDD         // <-----
import * from bat::Assertions
---
describe("User trades stocks") in [
  scenario("User requests a sell before close of trading") in [
    given("I have 100 shares of MSFT stock") in [
      POST `http://broker/create_stocks` with {
        body: {
          quantity: 100,
          paper: 'MSFT'
        }
      } assert [
        $.response.status == 201
      ]
    ],
    given("I have 150 shares of APPL stock") in [
      POST `http://broker/create_stocks` with {
        body: {
          quantity: 150,
          paper: 'APPL'
        }
      } assert [
        $.response.status == 201
      ]
    ],
    when("I ask to sell 20 shares of MSFT stock") in [
      POST `http://broker/sell_stocks` with {
        body: {
          quantity: 20,
          paper: 'MSFT'
        }
      } assert [
        $.response.status == 201
      ]
    ],
    should("have 80 shares of MSFT stock") in [
      GET `http://broker/get_stocks/MSFT` with {
        headers: {}
      } assert [
        $.response.status == 200,
        $.response.body.quantity == 80
      ]
    ],
    should("have 150 shares of APPL stock") in [
      GET `http://broker/get_stocks/APPL` with {
        headers: {}
      } assert [
        $.response.status == 200,
        $.response.body.quantity == 150
      ]
    ]
  ]
]

実行の制御およびアセットの削除

テスト中に失敗した場合、作成済みのアセットをすべて削除する必要があります。たとえば、ユーザーがアセットを作成し、検証を実行してから削除するとします。 通常、その検証は失敗し、これによってテストが中断されるため、アセットは削除されず、データベースにはテストデータが蓄積され始めます。一般的に、これによって ​must​ を ​should​ に変更することになり、実行を続けることはできますが、 失敗したアセットが残されることはありません。

正しくない例:

6 行目の名前の更新に失敗すると、実行が停止するためプロジェクトは削除されません。

describe `update project names` in [
  it must 'create a project' in [
    createProject()     // OK
  ],
  it must 'update the name' in [
    updateProjectName() // FAILS
  ],
  // Because the previous step is a `must` that failed, execution stops here and the next steps don't execute
  it must 'clean up deleting the project' in [
    deleteProject()     // CANCELLED
  ]
]

これらの予約された言葉を使用してステップを終了するか、実行を続けられるようにします。

  • should​ は何かが失敗する可能性があることを示しますが、テストに必須ではありません。

  • must​ はプロジェクトの作成など、何かの実行を続ける必要があることを示します。

正しい例:

describe `update project names` in [
  it must 'create a project' in [
    /**
     * Project creation is a MUST, because in this scenario
     * we depend on the created project to continue.
     */
    createProject()     // OK
  ],
  it should 'update the name' in [
    /**
     * Validations are should because the execution must continue
     * if the validation fails.
     */
    updateProjectName() // FAILS
  ],
  // Because the previous step is a should and it failed, continue executing.
  it must 'clean up deleting the project' in [
    deleteProject()     // OK
  ]
]

選択的なステップの実行

結果が失敗の場合、​assuming​ 関数はテストをスキップします。このコマンドは次の構文になります。

[TestBlockExpression] assuming [BooleanExpression] in …​

次に例を示します。

describe `E2E Scenario` in [
  it should 'always do something' in [
    doSomething()
  ],
  it should 'do something else' in [
    doSomethingElse()
  ],
  it should 'sometimes, do something else' assuming (random() > 0.5) in [
    // This is executed randomly, based on          ^^^^^^^^^^^^^^^^ that condition
    doSomethingElse()
  ],
  it should 'do something in dev environments' assuming (config.env == 'DEV') in [
    // This is executed only when                       ^^^^^^^^^^^^^^^^^^^^^ that == true
    doSomethingElse()
  ]
]

このコードをより理解しやすくするために、この関数の 2 つの別名を使用できます。 when​ と ​whenNot​ です。次に例を示します。

describe `E2E Scenario` in [
  it should 'always do something' in [
    doSomething()
  ],
  it must 'do something else' when config.runSanity in [
    doSomethingElse()
  ],
  it should 'do something else' when a == b in [
    doSomethingElse()
  ],
  it should 'do something in dev environments' whenNot config.isSmokeTests in [
    doSomethingElse()
  ]
]

ループ文の実行

while 関数または until 関数は、条件が true または false になる間 (または、それまで) テストを実行します。

while 関数のシグネチャー:

  • while( sentence , condition, time per request, number of retries)

  • do { sentence } while (condition) //→ Default values: 1 second and 3 retries​。

until 関数のシグネチャー:

  • until( sentence , condition, time per request, number of retries)

  • do { sentence } until (condition) //→ Default values: 1 second and 3 retries​。

例:

dwl
import * from bat::BDD
import * from bat::Assertions
import * from bat::Types
---
suite("Example for until and while") in [
  it should 'test the while prefix' in [
    while(() -> GET `http://apimon.cloudhub.io/users` with {},
    (x: BATHttpStep) -> x.result.response.status != 200, 10000, 5)
  ],
  it should 'test the while infix' in [
    do {
      () -> GET `http://apimon.cloudhub.io/users` with {} assert [
          $.response.status mustEqual 200
        ]
    } while ($.response.status != 200)
  ],
  it should 'test the until prefix' in [
    until(() -> GET `http://apimon.cloudhub.io/users` with {},
     (x: BATHttpStep) -> x.result.response.status != 200, 10000, 5)
  ],
  it should 'test the until infix' in [
    do {
      () -> GET `http://apimon.cloudhub.io/users` with {} assert [
          $.response.status mustEqual 200
        ]
    } until ($.result.response.status != 200)
  ]
]