これは何か?
先日、GDG Tokyo New Year LT大会 2021 で「E2E Testing for Google Assistant Apps」というテーマでLTをさせて頂いたのですが、5分では伝えきれない点があったので、こちらの記事に詳細を書くことにしました。
会話形アクションにこそ自動テストが必要
GoogleアシスタントアプリやAlexaスキルを作ったことがある方は、経験されているかもしれませんが、
会話型アクションをテストするのってすごく大変なんですね。
ちょっとインテントのフレーズを変えただけでも、その対象のシーンまで会話を持っていかないといけないので、何度も同じことを言ったりして労力を奪われてしまいます。
そこで必要になってくるのが End to End の自動テストです。
一度テストを作ってしまえば、改修のたびに都度都度、話しかけるという手間もなくなります。
Assistant Conversation Testing Library
Actions on Google には E2Eテストがコードベースで書けるライブラリが用意されています。
これは、Actions API をラップしていて、テストスイートを定義すれば、自分で作ったアクションにクエリを送信して、返ってきたレスポンスに対して検証することができます。
また、テスト実行前に Actions API の writePreview メソッドをコールすることで、Draftから自分のプロジェクトにプレビューを作成し、そこに対してテストすることができます。つまり、リリース前にひととおりの検証ができるってことですね。
Setup
Actions API を有効にする
従来のテストライブラリ
actions-on-google-testing-nodejs ではテスト対象の Action のプロジェクトとは別のプロジェクトの Actions API を使ってテストができたのですが、こちらはテスト対象のプロジェクトで Actions API を有効にする必要があります。
また、比較的最近作られた Actionプロジェクトではデフォルトで Actions API は有効になっているそうです。
- Google API Consoleにアクセス
- プロジェクトをテスト対象のものに切り替え
- "Actions API" を検索し、もしenabled になっていない場合は enable にする
Service Account Key を作成する
- 同じプロジェクトで、Credentials pageにアクセス
- "Create credentials" > "Service account" をクリック
- service account name に actions のテスト用だと分かるような名前をつけて Create
- Role に "Actions" > "Actions Admin" を指定する
- Service Account Key をJSONファイルでダウンロード
READMEには Role(権限) に project.OWNER を付けろって書いてあったけど、強すぎる。actions.ADMIN で動いたのでこちらがおすすめ
→ こちら PullRequest 出したら merge してもらえました!
環境変数 GOOGLE_APPLICATION_CREDENTIALS の設定
ダウンロードした JSON ファイルを環境変数に GOOGLE_APPLICATION_CREDENTIALS
セットします。
他のプロジェクトでは使いたくないものなので、direnv などを使って .envrc に記載しておくのがおすすめです。
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_account.json
ライブラリのインストール
npm install @assistant/conversation-testing --save # 以下は必要な人は入れる npm install @types/mocha --save npm install mocha --save npm install typescript --save
Test Code 作成
テストコードを書いていきます。
ここでは、今日食べるものを占ってくれる「カワウソ食べ物占い」のテストコードをサンプルにしています。
import 'mocha'; import {ActionsOnGoogleTestManager} from '@assistant/conversation-testing'; const PROJECT_ID = 'otter-fortune-telling-xxxxx'; const TRIGGER_PHRASE = 'カワウソ食べ物占いにつないで'; // 呼び出すフレーズ const DEFAULT_LOCALE = 'ja-JP'; // 各言語の指定 const DEFAULT_SURFACE = 'PHONE'; // 各デバイスの指定 describe('Action project', function () { // write Preview するときは set timeout したほうが良い this.timeout(60000); let test: ActionsOnGoogleTestManager; before('setup test suite', async function() { test = new ActionsOnGoogleTestManager({ projectId: PROJECT_ID }); await test.writePreviewFromDraft(); test.setSuiteLocale(DEFAULT_LOCALE); test.setSuiteSurface(DEFAULT_SURFACE); });
プロジェクトIDには、テストしたいアクションのプロジェクトIDを記載し、トリガーフレーズにはアクションを呼び出すフレーズを入れます。 ロケールやsurfaceでは、テストしたい言語やデバイスを指定することができます。
// おやつを占うテストパス it('should match Snack Type, and end the conversation', async function () { await test.sendQuery(TRIGGER_PHRASE); // TestManager を使って API レスポンスをアサーションする test.assertSpeech('カワウソたべもの占いへようこそ! 占ってほしいのはランチ?ディナー?それともおやつ?'); // ユーザークエリをアクションに返答する await test.sendQuery('おやつ'); // 正規表現を使ってアサーションする test.assertSpeech(`ふむふむ。おやつか〜。\nそんな君におすすめのおやつは.*楽しみだね〜!もう一回やる?`, {isRegexp: true}); // 会話を終了させる返答をする await test.sendQuery('やらない'); test.assertSpeech('じゃあまたね!'); // 会話が終了していることを確認する test.assertConversationEnded(); });
ここでは、テストスイートを定義しているのですが、お菓子を占ってもらって会話終了、という流れをテストしています。
このテストマネージャーには各種アサーションのメソッドが用意されているので、それを使ってレスポンスや、会話が終了していることを検証します。
もちろん正規表現やリストも使えるので、柔軟にテストは書いていくことができます。
// ランチを占ってから、もう一回占うテストパス it('should match Lunch Type, and again the conversation', async function () { await test.sendQuery(TRIGGER_PHRASE); test.assertSpeech('カワウソたべもの占いへようこそ! 占ってほしいのはランチ?ディナー?それともおやつ?'); await test.sendQuery('ランチ'); test.assertSpeech(`ふむふむ。ランチか〜。\nそんな君におすすめのおやつは.*楽しみだね〜!もう一回やる?`, {isRegexp: true}); // 会話を続ける返答をする await test.sendQuery('やる'); // シーンAgainに遷移したことを確認する test.assertScene('Again'); test.assertSpeech('占ってほしいのはランチ?ディナー?それともおやつ?'); // 会話が終了していないことを確認する test.assertConversationNotEnded(); });
このテストでは、占ったあとに会話を終了せずにシーン Againに遷移することをテストしています。
Run Tests
先程作成したテストコードのファイルは、src/test/ 下に配置します。 そして、package.json に以下の script コマンドを追加しておきます。
"scripts": { "test": "mocha --recursive --require ts-node/register src/test/*.ts", },
テストを実行します。
npm run test
出力結果
Failed した結果
「もう一回やる?」「やる」のあとに、シーン Again に遷移してほしいのに、会話が終了してしまい、期待値と異なるメッセージが返ってきています。 そして、コンソールには期待したレスポンスと実際に返ってきたレスポンスのDIFFが表示されます。
Pass した結果
トレーニングフレーズを修正すると、テストが通りました。
Assertions 各種
用意されているアサーションは、assertSpeech や assertScene だけではありません。 Assertions を確認してください。
- assertText - テキストレスポンスの検証
- assertCard, assertImage, assertTable, etc - 各表示要素の検証
- assertIntent - クエリにマッチしたインテントの検証
- assertSessionParam, assertUserParam, assertHomeParam - セッションストレージなどストレージへのパラメータの検証
ただ、まだビルトインインテントやトランザクションのテストは未対応となっているので、これから拡張されることを期待しています。
まとめ
- 会話型アクションのインテグレーションテストはとても大変
- でも自動テストがあれば改修も気軽にできる
- Actions on Google には E2Eテスト用のライブラリが提供されている
所感:やはりこれだけの内容を5分でやるのは無理があった・・・