AlexaでE2Eテストを書けるようにした話

研究開発部の伊尾木です。

研究開発部では、Alexaのスキルを公開しています(Google Assistantも公開していますよ!)。

今回はAlexaスキルのテストを便利にするKuchimaneというツールを公開したので紹介したいと思います。

E2Eテストが難しい

音声UIの開発はまだまだ新しい分野で知見やツールがそろっているわけではありません。 特に E2E (End To End) テスト、RSpecでいうところの Feature spec に相当するようなテストを行うことがとても困難でした。

AlexaでのE2Eテスト

以下のような一連の会話があったとします。

あなた「クックパッドを開いて」
Alexa「クックパッドへようこそ」
あなた「大根のレシピを教えて」
Alexa「大根ですね。サラダ、ナムル、スープのどのレシピがいいですか」
あなた「スープ」
Alexa「大根のスープですね。レシピを送信しました」

Alexaでは、この一連の会話「クックパッドを開いてから、レシピを送信するまで」をローカルでテストする方法がありません。 (Alexaのデモ環境に都度リクエストを投げればテストはできますが、やっぱりローカルだけでやりたいですよね)

会話の一部だけ、一回のやりとりだけのテストなら可能です。

例えば、「クックパッドを開いて」->「クックパッドへようこそ」 の組み合わせのみテストするといったことは可能です。 が、全部通したテストを書くことはできません。

通常のWebアプリのテストでいえば、 「一回のHTTPリクエストごとのテストは可能だけど、複数HTTPリクエスト、あるいは複数画面にまたがるテストが書けない」 という状況と同じです。

とても不便ですよね。

なぜできないのか

Alexaでは、ユーザの生の発話を、開発者が直接操作することはありません。 一旦、内部的なインテントとよばれるユーザの意図を表している処理に変換します。

例えば「クックパッドを開いて」という発話は、内部的に LaunchRequest というインテントに変換されて処理を実行します。 Webアプリとの対比でいえば、URLからコントローラ・アクション名に変換するルーティング処理と同じような感じです。

このルーティング処理が、Alexa内部に隠蔽されているため、ローカルでテストすることができないのです。 どうしてもローカルで一連の会話をテストしたい場合、ルーティング処理を自前で処理する必要があります。

Kuchimane

私達も当初、インテント単位のテストだけで乗り切ろうとしていましたが、複数インテントが絡む処理はテストできないため、エラーが起きやすい状況でした。

そこで、一連の会話をテストするための Kuchimane を開発しました!

じゃぁ Kuchimane では、さきほどのルーティング処理をどうしているのかというと、これまた自前で実装しています。

より正確にはsatori-flow というルーティング処理用のライブラリを開発しています。 KuchimaneがAlexaの会話モデル定義を解析し、このsatori-flowに「どんな発話がどのインテントになるのか」を登録します。

というわけで、以下のようなコードが書けるようになります!

const intents = { LaunchRequest, SearchDishIntent, SearchRecipeIntent };
const kuchimaneRunner = Kuchimane.runner(intents, __dirname + '/kuchimane_config.json');

it('searchRecipe', () => {
  return kuchimaneRunner.talkCheck('クックパッドを開いて', (message) => {
      expect(message).to.include('クックパッドですね')
    })()
    .then(kuchimaneRunner.talkCheck('大根のレシピを教えて', (message) => {
      expect(message).to.include('大根ですね。サラダ、ナムル、スープのどのレシピがいいですか');
    }))
    .then(kuchimaneRunner.talkCheck('スープ', (message) => {
      expect(message).to.include('大根のスープですね。レシピを送信しました');
    }))
  }
);

最初の行でintentsというオブジェクトを生成していますが、ここのLaunchRequestSearchDishIntentSearchRecipeIntent がインテント関数になります。 次にkuchimaneRunnerというインスタンスを、さきほどのintentsとKuchimane用の設定ファイル(Alexaのモデルへのパスなどを書く)から生成しています。

kuchimaneRunnertalkCheckというメソッドがE2Eテスト用のメソッドになります。第1引数がユーザの発話、第2引数がチェック用の関数になります。

talkCheckメソッドはユーザの発話を受け取ると、それを satori-flow に渡してインテント名に変換してもらいます。 そして、kuchimaneRunnerの生成時にもらったintentsの中から、インテント名にマッチする関数を取り出して実行し、Alexaのレスポンスをチェック用の関数に渡してテストを実行します。 最後にtalkCheckメソッドは、Promiseを返しますので、thenで会話を繋げていきます。

一連の会話をテストで書けることがわかりますね! 便利ですね!!

おわりに

AlexaのE2Eテストのための Kuchimane の紹介でした。

バグの多くは機能の組み合わせ部分に潜むと言われますが、実際私達も複数の会話、複数のインテントが絡む部分でよくエラーが起きていました。 Kuchimane以前は、このような部分をテストすることができなかったのですが、Kuchimaneのおかげで複数の会話が絡む部分をテストできるようになり 品質向上に一定の効果があるなと感じています。

ちなみに、まだまだKuchimaneの完成度は高くありません。例えばASK SDK v2 にも対応できていませんし、私達にとって必要な部分を優先的に実装しているため、フォローできていないケースもあります。これらの点については今後拡充していく予定です。

また現状ではGoogle Assistantに対応していませんが、こちらも今後対応する予定です!