ベスト プラクティスを試す

Actions on Google プラットフォーム用のアクションを開発するには、多くの場合、自然言語理解(NLU)用の Dialogflow と、アクションのロジックを処理する Dialogflow フルフィルメントを実装します。コードベースでテストを行うことにより、アクションが本番環境で期待どおりに動作することを確認できます。

アクションに単体テスト、統合テスト、エンドツーエンド テストを実装する場合、Dialogflow エージェントとフルフィルメントを別々のコンポーネントと見なす必要があります。

フローチャートは、ユーザークエリから Actions on Google、Dialogflow、フルフィルメント Webhook に進み、最終的にユーザーに返されます。

図 1. テストで考慮するシステムに関するフローチャート

Dialogflow エージェントをテストする

Dialogflow エージェントとフルフィルメントは、別々のコンポーネントとしてテストされます。以降のサブセクションでは、アクションの Dialogflow エージェントを概念化してテストする方法について説明します。

クエリが入り、インテントが出るシステムとして Dialogflow を捉える

Dialogflow エージェントは、ユーザーのクエリを取得してインテントとマッチングし、クエリから事前定義されたエンティティを抽出します。エージェントは、一致したインテント、そのパラメータ、および Actions on Google メタデータを含むメッセージを渡すことによって、フルフィルメントとやり取りします。

デベロッパーは、インテントやエンティティの構造と同様に、Dialogflow エージェントの構成を制御します。Actions on Google メタデータは、Actions on Google から取得され、テスト用の正しいデータが含まれていると想定できます。

テストする際は、エージェントがインテント パラメータを正しく抽出し、クエリとインテントをマッチングできるようにすることに焦点を当てます。このアプローチにより、エージェントのパフォーマンスを評価するための定量化可能な指標が提供されます。この指標は、個々のテストケースまたは検証セットを準備して使用することで計算できます。

Dialogflow エージェントは、入力としてテキストクエリ、出力としてインテントと抽出されたインテント パラメータを使用して表すことができます。

図 2. クエリが入り、インテントが出るシステムとして Dialogflow を表現

単体テスト

Dialogflow エージェントの場合は、各ケースで入力としてテキストクエリが受け取られ、出力としてインテント メタデータが生成されるテストを作成できます。このメタデータには、(少なくとも)一致したインテントの名前と、一致したパラメータのリストが含まれている必要があります。

Dialogflow API の detectIntent エンドポイントは、テキストクエリを入力として受け取り、解決されたインテントの名前と抽出されたパラメータを含む構造化出力を生成します。この出力は、エージェントのインテント マッチングのパフォーマンスを評価するのに役立ちます。他の有用なフィールドの完全なリファレンスについては、QueryResult リファレンスをご覧ください。

サンプルテストは次のようになります。

it('choose_fact', async function() {
  // The `dialogflow` variable is an abstraction around the API that creates
  // and sends payloads to Dialogflow.
  const resJson = await dialogflow.detectIntent(
    'Tell me about the history of Google');
  expect(resJson.queryResult).to.include.deep.keys('parameters');
  // Check that Dialogflow extracted required entities from the query.
  expect(resJson.queryResult.parameters).to.deep.equal({
    'category': 'history',
    // Put any other parameters you wish were extracted
  });
  expect(resJson.queryResult.intent.displayName).to.equal('choose_fact');
});

このスニペットでは MochaChai を使用しています。Facts About Google の Node.js で記述された Dialogflow 単体テストの実用的な例をご覧ください。

Dialogflow API は sessionId を引数として受け取るため、テストファイルを並行して実行できます。その結果、1 つの Dialogflow API クライアントを使用しながら、会話ごとに個別のサンドボックスを使用できます。

Dialogflow API に対してリクエストを行っているため、無料呼び出しの割り当てに達すると料金が発生する可能性があります。詳細については、割り当てと上限をご覧ください。

インテグレーション テスト

Dialogflow API の detectIntent エンドポイントは、サードパーティ フルフィルメントもトリガーします。そのため、Dialogflow エージェントと Dialogflow フルフィルメントの統合をカバーするテストケースを作成できます。

Dialogflow の統合テストと単体テストの作成の主な違いは、統合テストでは、Dialogflow インテントとエンティティの抽出だけでなく、Webhook からのレスポンスもアサートできることです。

Facts About Google リポジトリの Node.js で作成された統合テストの完全に機能する例をご覧ください。

Dialogflow フルフィルメント Webhook をテストする

Dialogflow エージェントと Dialogflow フルフィルメントは別々のコンポーネントとしてテストされます。以降のサブセクションで、アクションのフルフィルメントを概念化してテストする方法について説明します。

JSON が入り、JSON が出るシステムとしてフルフィルメントを捉える

Dialogflow フルフィルメント コードは、リクエストを想定し、JSON 形式でレスポンスを生成します。その結果、フルフィルメント コードを JSON 入力システムと JSON 出力システムと見なしてテストできます。リクエストには、Dialogflow と Actions on Google の両方のメタデータが含まれているため、フルフィルメントで特定のインテント ハンドラをトリガーするために必要なものがすべて揃っています。

インテント ハンドラのトリガーをテストするには、アクションに JSON リクエスト(入力)を送信します。このリクエストは、インターネット上でアクセス可能なフルフィルメントに渡されます。その後で、フルフィルメントが、検証用として評価可能な JSON レスポンス(出力)を生成します。

フルフィルメントは、JSON リクエスト入力と Webhook JSON レスポンス出力で表すことができます。

図 3. JSON が入り、JSON が出るシステムとして Dialogflow を表現

単体テスト

フルフィルメント Webhook を、JSON 入力を受け取って JSON 出力を生成するシステムと考えます。そうすれば、アクションのテストプロセスが、リクエストをフルフィルメントに提供し、結果の出力 JSON を確認するだけに簡略化されます。

これにより、自由に、フルフィルメントをローカルでホストし、テスト用に HTTP リクエストをローカルで送信できます。Actions on Google Node.js クライアント ライブラリを使用している場合は、JSON リクエストをクライアント ライブラリ ミドルウェア レイヤに直接送信することもできます。

JSON 入力を使用して Webhook コードをテストし、想定される JSON 出力を受け取れば、制御する部分が正しく機能することを保証できます。正しい JSON ペイロードが生成されるため、Dialogflow と Actions on Google が正しく機能していると考えることができます。この分離によって、テストを作成するための簡略化されたプログラミング モデルが提供されます。

テストプロセスの概要を以下に示します。

  1. Actions Console のシミュレータを使用して、ユースケースの各ステップで JSON リクエストを取得します。それらを JSON ファイルとして保存します。また、Webhook リファレンス ドキュメントの情報を使用して、自分でリクエストを作成することもできます。
  2. これらのペイロードを使って Webhook を呼び出すテストを作成します。
  3. テストごとに、レスポンス JSON に想定されたアイテムが含まれていることを確認します。

さらに、このモデルでは、フルフィルメント エンドポイントをローカルで実行でき、Dialogflow API にセッションのコンセプトが組み込まれているため、継続的インテグレーションで Dialogflow フルフィルメントをテストできます。

サンプルテストは次のようになります。

it('yes-history', function() {
  expect(jsonRes.payload).to.have.deep.keys('google');
  expect(jsonRes.payload.google.expectUserResponse).to.be.true;
  expect(jsonRes.payload.google.richResponse.items).to.have.lengthOf(3);
  expect(jsonRes.payload.google.richResponse.suggestions).to.have
    .deep.members([
      {'title': 'Sure'}, {'title': 'No thanks'},
    ]);
});

上記のスニペットでは、MochaChai を使用しています。Node.js で記述された完全に機能する例については、Facts About Google リポジトリをご覧ください。

単体テスト可能なフルフィルメントの設計

Webhook コードの多くには、アプリケーションのニーズを満たすために必要なカスタム ビジネス ロジックが含まれています。加えて、Webhook コードには、インテント ハンドラも含めることができます。

フルフィルメント コードの単体テストの粒度を高めるには、ビジネス ロジックをインテント処理ルーチンから切り離すような方法でコードを整理することをおすすめします。つまり、インテント ハンドラとビジネス ロジックを別々のモジュールに含めることで、それぞれを個別にテストできます。

例については、GitHub の shiritori サンプル アクションをご覧ください。このサンプルでは、functions/index.jsfunctions/shiritori/*.js にインテント ハンドラとビジネス ロジックが別々に含まれているため、より堅牢なテストスイートが可能になります。

インテグレーション テスト

Dialogflow とフルフィルメント Webhook コードの統合をカバーするテストケースを作成する場合は、上記の Dialogflow の統合テスト セクションをご覧ください。

負荷テスト

アクションを本番環境にデプロイする前に、Webhook フルフィルメントの負荷テストを行い、フルフィルメント サービスの低下や中断を引き起こすパフォーマンスの問題を明らかにすることをおすすめします。

負荷テストで発見できるパフォーマンス上の問題の例を以下に示します。

  • コンピューティング能力とメモリが限られている
  • プロバイダからの割り当て制限
  • データの読み込みと書き込みが遅い
  • コード内の同時実行の問題

負荷テストのシナリオは、アクションの想定される使用パターンや過去の使用パターンによって異なりますが、一般的なテストシナリオは負荷の急激な増加(急増)と持続的な負荷(ソーク)です。

Webhook が呼び出され、リソースを大量に消費するオペレーションを実行するシナリオを特定します。リソースを大量に消費するオペレーションには、データベースのクエリ、別の API の呼び出し、コンピューティングの実行、サウンド ファイルのレンダリングなどメモリ使用量の多いオペレーションなどがあります。

このようなシナリオでは、Actions on Google サーバーから Webhook に送信されたリクエストを、Webhook ログまたは Stackdriver ログからキャプチャできます。また、Actions Console シミュレータからリクエストをキャプチャすることもできます。

リクエストを取得したら、負荷テストツールを使用して、さまざまな負荷テストシナリオで Webhook がどのように応答するかを調べることができます。以降のサブセクションでは、ApacheBench を使用したスパイクテストとソークテストの例を示します。

スパイクテスト

スパイクテストでは、一定期間、一定数のリクエストを Webhook に送信し、負荷を突然増やす必要があります。たとえば、10 QPS(秒間クエリ数)の負荷を、わずかに 60 QPS の急増に送信するテストを設定できます。

次の ApacheBench コマンドを実行すると、60 件の同時リクエストを Webhook に送信できます。

ab -n 60 -c 60 -p ActionRequest.json -T 'application/json' https://example.com/webhookFunctionName

たとえば、ActionRequest.json ファイルに、キャプチャされた Webhook に送信されたリクエスト ペイロードが含まれているとします。

ソークテスト

ソークテストでは、一定数のリクエストを Webhook に送信し、レスポンスをモニタリングする必要があります。たとえば、10 ~ 20 QPS の負荷を数分間送信してレスポンス時間が増加するかどうかを確認するテストを設定できます。

次の ApacheBench コマンドを実行すると、1 秒あたり 10 件の同時リクエストを使用して、1, 200 件のリクエストを送信できます。

ab -t 120 -n 1200 -p ActionRequest.json -T 'application/json' https://example.com/webhookFunctionName

たとえば、ActionRequest.json ファイルに、キャプチャされた Webhook に送信されたリクエスト ペイロードが含まれているとします。

負荷テストの結果の分析

負荷テストを実行した後、Webhook レスポンス時間の結果を分析します。通常、Webhook の実装の問題を示す指標としては、テストの実行ごとに増加する応答時間の中央値や、アクションにとって許容できない最悪のケースの応答時間などが挙げられます。

エンドツーエンドのテスト

承認のためにアクションを送信する前のエンドツーエンド テストでは、Actions Console シミュレータが使用されます。Actions シミュレータのドキュメントで、Actions Console シミュレータを使用してエンドツーエンド テストを行う手順を確認できます。これらのテストを行うと、Actions on Google インフラストラクチャ コンポーネントから潜在的な不確実性を排除できます。