kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

AlexaスキルのテストをBotiumでやってみた①

f:id:kun432:20210131175240p:plain

Alexaスキルのテスト、どうしてますか?色々やり方はあると思います。

今回はVoiceflowのcommunity forumで知った「Botium」を使ってみたいと思います。

目次

Botiumとは?

Botiumはチャットボットのテスト自動化プラットフォームです。

無料プランはありますが、E2Eや音声テストなどは有償プランじゃないと使えません。有償プランの価格は載ってないですけど、ちょっと調べてみた感じ、そこそこかかるみたいですね・・・・

ただし、ツールライブラリだけを使うなら無料かつOSSになっているので、今回はそちらでやってみましょう。

Botiumのツール

Botiumのツールは以下のようになっています。

  • Botium Core
    • Botiumのコアライブラリ
  • Botium CLI
    • Botium Coreを使ったCLI。テストもできます。
  • Botium Bindings
    • Botium Coreとテストランナーをつなげるもの。テストランナーにはMocha/Jasmin/Jestなどが使える。Botium CLIを使わずに、自前でテストランナーをつかいた場合はこちらを選ぶようです。
  • Botium Box
    • BotiumのCLIやライブラリを制御するためのGUIプラットフォーム。有償。

あと、上記には書いてないですが、以下もあります。

  • Botium Connector
    • チャットボットやフレームワーク、プラットフォームなどとつなげるためのもの。Alexaの場合はbotium-connector-alexa-smapiを使ってつなげる。

今回使うのは以下のみです。

  • botium-cli(botium core含む)
  • botium-connector

Botiumのインストールと基本的な使い方

botium-cliをインストールします。

$ npm install -g botium-cli

テストプロジェクトを初期化します。ディレクトリを作成してそこで行うのが良いでしょう。

$ mkdir alexa-test-sample && cd alexa-test-sample

$ botium-cli init
Botium Configuration File written to "./botium.json".
Botium Convo File written to "/path-to-somewhere/alexa-test-sample/give_me_a_picture.convo.txt".
Botium initialization ready. You should now run "botium-cli run --verbose --convos ." to verify.

botium-cli initを実行するとテストの雛形が作成されます。

$ tree
.
├── botium.json
└── give_me_a_picture.convo.txt

botium.jsonにテストプロジェクトの設定を記載します。デフォルトで用意されているテスト(botium-connector-echo)の設定が行われているようです。

{
  "botium": {
    "Capabilities": {
      "PROJECTNAME": "My Botium Project",
      "CONTAINERMODE": "echo"
    },
    "Sources": {},
    "Envs": {}
  }
}

.convo.txtがテストシナリオを記載するファイルになります。botium-cliは実行時に現在のディレクトリから*.convo.txtを探してテストを行うようになっているようですね。こちらもデフォルトで用意されているテスト用のシナリオが記載されているようですが、見て分かる通り、mochaやjestなどと違って、非常に簡易かつわかりやすい感じの記述になっていますね。

give me picture

#me
Hello, Bot!

#bot
You said: Hello, Bot!

#me
give me a picture

#bot
Here is a picture
MEDIA logo.png

では早速実行してみましょう。

$ botium-cli run --verbose --convos
  botium-cli Using Botium configuration file ./botium.json +0ms
  botium-cli-run command options: {
  botium-cli-run   _: [ 'run' ],
  botium-cli-run   verbose: true,
  botium-cli-run   v: true,
  botium-cli-run   convos: [ '.' ],
  botium-cli-run   C: [ '.' ],
  botium-cli-run   config: './botium.json',
  botium-cli-run   c: './botium.json',
  botium-cli-run   output: 'spec',
  botium-cli-run   testsuitename: 'Botium Test-Suite',
  botium-cli-run   n: 'Botium Test-Suite',
  botium-cli-run   expandutterances: false,
  botium-cli-run   expandscriptingmemory: false,
  botium-cli-run   timeout: 60,
  botium-cli-run   '$0': '../../.volta/tools/image/packages/botium-cli/bin/botium-cli'
  botium-cli-run } +0ms
  botium-cli-run Mocha Reporter "spec", options: undefined +0ms
  botium-core-BotDriver Loaded Botium configuration files /path-in-somewhere/alexa-test/botium.json +0ms
  botium-core-ScriptingProvider ReadConvosFromDirectory(.) found filenames: botium.json,give_me_a_picture.convo.txt +0ms
  botium-core-ScriptingProvider ReadConvosFromDirectory(.) found convos:
  botium-core-ScriptingProvider  1 give me picture ({ convoDir: '.', filename: 'give_me_a_picture.convo.txt' }): Line 3: #me - Hello, Bot! | Line 6: #bot - You said: Hello, Bot! | Line 9: #me - give me a picture | Line 12: #bot - Here is a picture MEDIA(logo.png) +4ms
  botium-core-ScriptingProvider ReadConvosFromDirectory(.) found utterances:
  botium-core-ScriptingProvider  none +1ms
  botium-core-ScriptingProvider ReadConvosFromDirectory(.) found partial convos:
  botium-core-ScriptingProvider  none +0ms
  botium-core-ScriptingProvider ReadConvosFromDirectory(.) scripting memories:
  botium-core-ScriptingProvider  none +0ms
  botium-cli-run ready reading convos (1), expanding convos ... +238ms
  botium-core-ScriptingProvider ExpandConvos - Using utterances expansion mode: all +0ms
  botium-cli-run ready expanding convos and utterances, number of test cases: (1). +23ms
  botium-cli-run adding test case give me picture (from: { convoDir: '.', filename: 'give_me_a_picture.convo.txt' }) +2ms


  Botium Test-Suite
  botium-core-BotDriver Build - Botium Core Version: 1.11.15 +280ms
  botium-core-BotDriver Build - Capabilites: {
  botium-core-BotDriver   PROJECTNAME: 'My Botium Project',
  botium-core-BotDriver   TESTSESSIONNAME: 'Botium Test Session',
  botium-core-BotDriver   TESTCASENAME: 'Botium Test Case',
  botium-core-BotDriver   TEMPDIR: 'botiumwork',
  botium-core-BotDriver   CLEANUPTEMPDIR: true,
  botium-core-BotDriver   WAITFORBOTTIMEOUT: 10000,
  botium-core-BotDriver   SIMULATE_WRITING_SPEED: false,
  botium-core-BotDriver   SIMPLEREST_PING_RETRIES: 6,
  botium-core-BotDriver   SIMPLEREST_PING_TIMEOUT: 10000,
  botium-core-BotDriver   SIMPLEREST_PING_VERB: 'GET',
  botium-core-BotDriver   SIMPLEREST_PING_UPDATE_CONTEXT: true,
  botium-core-BotDriver   SIMPLEREST_PING_PROCESS_RESPONSE: false,
  botium-core-BotDriver   SIMPLEREST_INIT_PROCESS_RESPONSE: false,
  botium-core-BotDriver   SIMPLEREST_STOP_RETRIES: 6,
  botium-core-BotDriver   SIMPLEREST_STOP_TIMEOUT: 10000,
  botium-core-BotDriver   SIMPLEREST_STOP_VERB: 'GET',
  botium-core-BotDriver   SIMPLEREST_START_RETRIES: 6,
  botium-core-BotDriver   SIMPLEREST_START_TIMEOUT: 10000,
  botium-core-BotDriver   SIMPLEREST_START_UPDATE_CONTEXT: true,
  botium-core-BotDriver   SIMPLEREST_START_PROCESS_RESPONSE: true,
  botium-core-BotDriver   SIMPLEREST_START_VERB: 'GET',
  botium-core-BotDriver   SIMPLEREST_POLL_VERB: 'GET',
  botium-core-BotDriver   SIMPLEREST_POLL_INTERVAL: 1000,
  botium-core-BotDriver   SIMPLEREST_POLL_UPDATE_CONTEXT: true,
  botium-core-BotDriver   SIMPLEREST_METHOD: 'GET',
  botium-core-BotDriver   SIMPLEREST_IGNORE_EMPTY: true,
  botium-core-BotDriver   SIMPLEREST_TIMEOUT: 10000,
  botium-core-BotDriver   SIMPLEREST_EXTRA_OPTIONS: {},
  botium-core-BotDriver   SIMPLEREST_STRICT_SSL: true,
  botium-core-BotDriver   SIMPLEREST_INBOUND_UPDATE_CONTEXT: true,
  botium-core-BotDriver   SIMPLEREST_CONTEXT_MERGE_OR_REPLACE: 'MERGE',
  botium-core-BotDriver   SCRIPTING_TXT_EOL: '\n',
  botium-core-BotDriver   SCRIPTING_XLSX_EOL_WRITE: '\r\n',
  botium-core-BotDriver   SCRIPTING_XLSX_HASHEADERS: true,
  botium-core-BotDriver   SCRIPTING_CSV_SKIP_HEADER: true,
  botium-core-BotDriver   SCRIPTING_CSV_QUOTE: '"',
  botium-core-BotDriver   SCRIPTING_CSV_ESCAPE: '"',
  botium-core-BotDriver   SCRIPTING_NORMALIZE_TEXT: true,
  botium-core-BotDriver   SCRIPTING_ENABLE_MEMORY: false,
  botium-core-BotDriver   SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS: false,
  botium-core-BotDriver   SCRIPTING_MATCHING_MODE: 'wildcardIgnoreCase',
  botium-core-BotDriver   SCRIPTING_UTTEXPANSION_MODE: 'all',
  botium-core-BotDriver   SCRIPTING_UTTEXPANSION_RANDOM_COUNT: 1,
  botium-core-BotDriver   SCRIPTING_UTTEXPANSION_NAMING_MODE: 'justLineTag',
  botium-core-BotDriver   SCRIPTING_UTTEXPANSION_NAMING_UTTERANCE_MAX: '16',
  botium-core-BotDriver   SCRIPTING_MEMORYEXPANSION_KEEP_ORIG: false,
  botium-core-BotDriver   SCRIPTING_FORCE_BOT_CONSUMED: false,
  botium-core-BotDriver   ASSERTERS: [],
  botium-core-BotDriver   LOGIC_HOOKS: [],
  botium-core-BotDriver   USER_INPUTS: [],
  botium-core-BotDriver   SECURITY_ALLOW_UNSAFE: true,
  botium-core-BotDriver   CONTAINERMODE: 'echo',
  botium-core-BotDriver   CONFIG: './botium.json'
  botium-core-BotDriver } +0ms
  botium-core-BotDriver Build - Sources : { LOCALPATH: '.', GITPATH: 'git', GITBRANCH: 'master', GITDIR: '.' } +1ms
  botium-core-BotDriver Build - Envs : { IS_BOTIUM_CONTAINER: true } +0ms
  botium-connector-PluginConnectorContainer-helper Botium plugin botium-connector-echo loaded. Plugin version is 0.0.16 +0ms
  botium-cli-run running testcase give me picture +56ms
  botium-core-Convo give me picture/Line 3: user says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "me",
  botium-core-Convo   "channel": null,
  botium-core-Convo   "not": false,
  botium-core-Convo   "optional": false,
  botium-core-Convo   "messageText": "Hello, Bot!",
  botium-core-Convo   "media": null,
  botium-core-Convo   "buttons": null,
  botium-core-Convo   "cards": null,
  botium-core-Convo   "forms": null,
  botium-core-Convo   "attachments": null,
  botium-core-Convo   "asserters": [],
  botium-core-Convo   "userInputs": [],
  botium-core-Convo   "logicHooks": []
  botium-core-Convo } +0ms
  botium-connector-echo UserSays called, echo back +0ms
  botium-core-Convo give me picture wait for bot  +1ms
  botium-core-Convo give me picture: bot says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "bot",
  botium-core-Convo   "messageText": "You said: Hello, Bot!",
  botium-core-Convo   "channel": "default"
  botium-core-Convo } +1ms
  botium-core-ScriptingMemory fill start: {} +0ms
  botium-core-ScriptingProvider assertBotResponse give me picture/Line 6 (Line 3: #me - Hello, Bot!) BOT: You said: Hello, Bot! = You said: Hello, Bot! ... +86ms
  botium-core-Convo give me picture/Line 9: user says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "me",
  botium-core-Convo   "channel": null,
  botium-core-Convo   "not": false,
  botium-core-Convo   "optional": false,
  botium-core-Convo   "messageText": "give me a picture",
  botium-core-Convo   "media": null,
  botium-core-Convo   "buttons": null,
  botium-core-Convo   "cards": null,
  botium-core-Convo   "forms": null,
  botium-core-Convo   "attachments": null,
  botium-core-Convo   "asserters": [],
  botium-core-Convo   "userInputs": [],
  botium-core-Convo   "logicHooks": []
  botium-core-Convo } +2ms
  botium-connector-echo UserSays called, echo back +3ms
  botium-core-Convo give me picture wait for bot  +0ms
  botium-core-Convo give me picture: bot says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "bot",
  botium-core-Convo   "messageText": "Here is a picture",
  botium-core-Convo   "media": [
  botium-core-Convo     {
  botium-core-Convo       "altText": "Botium Logo",
  botium-core-Convo       "mediaUri": "https://www.botium.ai/wp-content/uploads/2020/03/logo.png"
  botium-core-Convo     }
  botium-core-Convo   ],
  botium-core-Convo   "nlp": {
  botium-core-Convo     "intent": {
  botium-core-Convo       "name": "picture",
  botium-core-Convo       "confidence": 0.8
  botium-core-Convo     }
  botium-core-Convo   },
  botium-core-Convo   "channel": "default"
  botium-core-Convo } +1ms
  botium-core-ScriptingMemory fill start: {} +3ms
  botium-core-ScriptingProvider assertBotResponse give me picture/Line 12 (Line 9: #me - give me a picture) BOT: Here is a picture = Here is a picture ... +3ms
  botium-cli-run give me picture ready, calling done function. +10ms
    ✔ give me picture
  botium-connector-BaseContainer Cleanup rimrafing temp dir /Users/kun432/repository/alexa-test/botiumwork/My Botium Project 20220321 223340 fLHQW +0ms


  1 passing (54ms)

いろいろ出力されますが、最後に1 passingと表示されれば、convo.txtのシナリオ通りにボットとの会話が行われたということになります。

これでサンプルのテスト雛形は不要なのですべて削除してください。

$ rm -rf *

Alexaスキル向けテストプロジェクトの作成

Alexa用のbotium connectorをインストールします。

$ npm install -g botium-connector-alexa-smapi

Alexaスキル向けテストプロジェクトの初期化を行います。

$ botium-connector-alexa-smapi-cli init

ここから対話形式で設定を行います。画面の説明に従って設定を行えばOKです。順に行きましょう。

This wizard will guide you through the Botium Connector setup. Please follow the instructions.
It involves Copy&Paste from a web browser to this terminal window.



######## Step 1/3 - Create Amazon Security Profile ########
 1. Go to this url: https://developer.amazon.com/home.html
 2. Open "Settings" / "Security Profiles" and create a new profile or select an existing one
 3. Add this url to the "Allowed Return URLs": https://s3.amazonaws.com/ask-cli/response_parser.html


Copy & Paste the "Client-ID" here:

ターミナルを一旦このままにしておいて、https://developer.amazon.com/home.htmlにアクセス、ログインして、「設定」→「セキュリティプロファイル」と進み、既存のセキュリティプロファイルを選択するか、セキュリティプロファイルを新規作成します。ここでは新規に作成してみましょう。

f:id:kun432:20220321225052p:plain

f:id:kun432:20220321225100p:plain

f:id:kun432:20220321225109p:plain

セキュリティプロファイル名と説明を適当に記載して、保存します。

f:id:kun432:20220321225336p:plain

セキュリティプロファイルが作成されたら、「ウェブ設定」のタブをクリックします。

f:id:kun432:20220321225831p:plain

右下の編集をクリックします。

f:id:kun432:20220321225959p:plain

「許可された返信URL 」に先ほどターミナルに表示されていたURL(https://s3.amazonaws.com/ask-cli/response_parser.html)を入力して、保存します。

f:id:kun432:20220321230045p:plain

次に、セキュリティプロファイルのウェブ設定で表示されている「クライアントID」をコピーして、

f:id:kun432:20220321230443p:plain

ターミナルに貼り付けてENTER。

Copy & Paste the "Client-ID" here: amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

次にクライアントシークレットについて聞かれます。

Copy & Paste the "Client-Secret" here:

「シークレットを表示」をクリック

f:id:kun432:20220321231139p:plain

表示された「クライアントシークレット」をコピーして、

f:id:kun432:20220321231001p:plain

ターミナルに貼り付けてENTER。

Copy & Paste the "Client-Secret" here: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

次に表示されているURLにアクセスします。

######## Step 2/3 - Get Amazon Authorization Code ########
 1. Paste the following url to your browser and follow the instructions:
    https://www.amazon.com/ap/oa?response_type=code&client_id=amzn1.application-oa2-client.(...snip...)

Copy&Paste the Authorization Code you received:

Alexa開発者アカウント(amazon.co.jpアカウント)でログインします。

f:id:kun432:20220321231900p:plain

権限を聞かれるので許可します。

f:id:kun432:20220321232021p:plain

認証コードが生成されますので、これをコピーして、

f:id:kun432:20220321232030p:plain

ターミナルに貼り付けてENTER。

Copy&Paste the Authorization Code you received: XXXXXXXXXXXXXXXXX

Alexa開発者コンソール上で作成しているスキルが一覧表示されますので、テストを行うスキルの番号を入力します。今回はテスト用に用意したスキルを選択しました。

######## Step 3/3 - Selecting vendor id and skill ########


Using vendor id "XXXXXXXXXXXXXX" (hoge) for your account


Found 50 skills for your account
 1: "テストスキル1" (amzn1.ask.skill.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX1)
 2: "テストスキル2" (amzn1.ask.skill.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX2)
...snip...
 19: "トリップアドバイザー" (amzn1.ask.skill.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX3)
...snip...
Enter Number of skill to use: 19

以下のように表示されればOKです。入力した情報をもとにbotium.jsonが生成されます。

######## Ready - Creating botium.json ########
Done.

中身を少し見てみましょう。

{
  "botium": {
    "Capabilities": {
      "CONTAINERMODE": "alexa-smapi",
      "ALEXA_SMAPI_REFRESHTOKEN": "XXX(...snip...)XXX",
      "ALEXA_SMAPI_CLIENTID": "amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "ALEXA_SMAPI_CLIENTSECRET": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "ALEXA_SMAPI_VENDORID": "XXXXXXXXXXXXXX",
      "ALEXA_SMAPI_SKILLID": "amzn1.ask.skill.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX3"
    }
  }
}

CONTAINERMODEalexa-smapiになっていて、対話形式で入力したものが設定されているのがわかると思います。少しだけ追加します。

{
  "botium": {
    "Capabilities": {
      "PROJECTNAME": "トリップアドバイザー",      // 追加
      "ALEXA_SMAPI_LOCALE": "ja-JP",     // 追加
      "CONTAINERMODE": "alexa-smapi",
      "ALEXA_SMAPI_REFRESHTOKEN": "XXX(...snip...)XXX",
      "ALEXA_SMAPI_CLIENTID": "amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "ALEXA_SMAPI_CLIENTSECRET": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "ALEXA_SMAPI_VENDORID": "XXXXXXXXXXXXXX",
      "ALEXA_SMAPI_SKILLID": "amzn1.ask.skill.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX3"
    }
  }
}

PROJECTNAMEはテストプロジェクトの名前です。これはあってもなくてもいいです。ALEXA_SMAPI_LOCALEはスキルのロケールです。これが定義されていない場合、どうやらen-USでテストが行われ、en-US向けにスキルが作成されていない場合は失敗します。日本語の場合はja-JPを設定しておきます。なお、説明のためにコメントを入れていますが、JSONではコメントが使えないので実際に設定する際には入れないようにしてください。

Botiumを使ったAlexaスキルのテスト

ではテストシナリオを作りましょう。その前にサンプルのスキルの会話フローをご紹介します。今回のサンプルはVoiceflowで作成しています。

f:id:kun432:20220321234411p:plain

行きたい都市名を言うと、おすすめの観光地を教えてくれるというものです。実際に作る場合はもっと複雑になると思いますが、テストのためのサンプルなので。ハッピーパスはこういう感じになります。

f:id:kun432:20220321234806p:plain

ではテストシナリオです。tripadvisor_happypath01.convo.txtという名前でシナリオを作りました。*.convo.txtであればなんでもよいです。

トリップアドバイザーのテスト:Happy Path #1

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
京都 かな

#bot
京都 ですね。京都 は清水寺がおすすめです。

注意する点としては、スロットおよび変数の前後には半角スペースが必要になります。これはVoiceflowの仕様によるかもしれません(Voiceflowでは必ず必要になる。通常のAlexaスキルだと不要かも)

ではテストを実行してみましょう。

$ botium-cli run


  Botium Test-Suite
    ✔ トリップアドバイザーのテスト:Happy Path #1 (10767ms)


  1 passing (12s)

はい、問題なくテスト成功していますね。

失敗の場合も見てみましょう。シナリオファイルを少し修正します。

#bot
京都 ですね。京都 は東大寺がおすすめです。

実際にはシナリオ側が間違っているのですが、エラーの確認のため、ということで。実行してみましょう。

$ botium-cli run


  Botium Test-Suite
    1) トリップアドバイザーのテスト:Happy Path #1


  0 passing (12s)
  1 failing

  1) Botium Test-Suite
       トリップアドバイザーのテスト:Happy Path #1:
     Error: トリップアドバイザーのテスト:Happy Path #1/Line 12: Bot response (on Line 9: #me - 京都 かな) "京都 ですね。京都 は清水寺がおすすめです。" expected to match "京都 ですね。京都 は東大寺がおすすめです。"
########################################
ASSERTION FAILED in TextMatchAsserter - Expected: ["京都 ですね。京都 は東大寺がおすすめです。"]  - Actual: "京都 ですね。京都 は清水寺がおすすめです。"
INPUT: 京都 かな
------------ TRANSCRIPT ----------------------------
#me: トリップアドバイザーを開いて
#bot: <speak>はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。</speak>
#me: 京都 かな
#bot: <speak>京都 ですね。京都 は清水寺がおすすめです。</speak>
      at wrapBotiumError (/path-in-somewhere/botium-cli/lib/node_modules/botium-cli/src/run/index.js:76:12)
      at finish (/path-in-somewhere/botium-cli/lib/node_modules/botium-cli/src/run/index.js:205:24)
      at /path-in-somewhere/botium-cli/lib/node_modules/botium-cli/src/run/index.js:218:11

はい、想定とテスト結果が異なっていることがわかりますね。このようにしてテストを行えばよいわけです。

また、テストファイルは複数用意することができます。同じパスに以下のファイルを追加します。

tripadvisor_happypath02.convo.txt

トリップアドバイザーのテスト:Happy Path #2

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
奈良 に行きたい

#bot
奈良 ですね。奈良 は東大寺がおすすめです。

tripadvisor_nomatch_city.convo.txt

トリップアドバイザーのテスト:都市名該当なし

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
神戸

#bot
ごめんなさい、神戸 はまだ対応していません。

では実行してみましょう。

$ botium-cli run


  Botium Test-Suite
    ✔ トリップアドバイザーのテスト:Happy Path #1 (10344ms)
    ✔ トリップアドバイザーのテスト:Happy Path #2 (10557ms)
    ✔ トリップアドバイザーのテスト:都市名該当なし (7728ms)


  3 passing (29s)

こんな感じでまとめてテストを実行してくれます。

まとめ

Botiumのシナリオはとても簡易でわかりやすいですね。mocha/jestなどは多少なりともコードの書き方を覚えておく必要がありますが、Botiumなら開発者じゃなくてもテスト用のシナリオをかけそうです。

とはいえ、これだけだと複雑なテストを書くには足りないですね。ということで、次回はシナリオの書き方についていろいろ見ていきたいと思います。

Voltaでnpmパッケージのグローバルインストール

f:id:kun432:20220319174947p:plain

前回、npmパッケージのグローバルインストールが期待したとおりの動きにならなかったVoltaだけど、

ちょっとやりなおしてみて、やりたいことはできたのでまとめます。前提として、自分なりの理解なので間違ってるかもしれませんが。

目次

Voltaにおけるパッケージのグローバルインストールの考え方

あらためてVoltaのブログを見てみた。

  • By running volta install mocha, you let Volta know that you want mocha to be available everywhere in your terminal. Volta will download the latest version (or if you want you can specify a specific version) to use as the default.
  • From that point forward, you’ll be able to run that default version of mocha directly in your terminal, just as if you had globally installed it with npm.
  • However, if you run mocha in a project that has the mocha package as a dependency, Volta will automatically delegate to the local version installed in node_modules, without you having to change the command at all.

あと、もう一つ。volta install パッケージ名はどうも以前のバージョンでのやり方みたいで、今は普通にnpm/yarnが使えるようになっている。

Installing global packages with Volta has many benefits over traditional globals. Until now, to take advantage of those benefits, you needed to install the packages using volta install instead of your package manager. With Volta 0.9.0, you can now install packages directly using your package manager and still get the upside that Volta provides:

これをまとめるとこうなる。

  • npm install -g パッケージ名を実行すると、Voltaはそのパッケージをグローバルに実行されるツールとして認識し、そしてデフォルトのツールセットとしてインストールする。
  • プロジェクト内でローカルインストールされたパッケージがある場合は、Voltaはグローバルにインストールされたものとは別の物と認識して、こちらを優先する。

前回イメージしていたものとは違ったのはこのあたりなのかも、ということでやってみる。

グローバルにインストールするパッケージをプロジェクトごとに分ける

Voltaでnodeを複数バージョンインストールする。最終的なデフォルトはnode@17.7.2。

$ volta install node@16.14.1
success: installed and set node@16.14.1 (with npm@8.5.0) as default
$ volta install node@16.14.2
success: installed and set node@16.14.2 (with npm@8.5.0) as default
$ volta install node@latest
success: installed and set node@17.7.2 (with npm@8.5.2) as default

$ volta list all
⚡️ User toolchain:

    Node runtimes:
        v16.14.1
        v16.14.2
        v17.7.2 (default)

    Package managers:


    Packages:

プロジェクトを作る

$ for i in a b
> do
> mkdir proj_${i} && cd proj_${i} && npm init -y && cd ..
> done

proj_aはnode.js-16.14.1、proj_bはnode.js-16.4.2でvolta pinしておく。

$ cd proj_a
$ volta pin node@16.14.1
success: pinned node@16.14.1 (with npm@8.5.0) in package.json
$ volta list
⚡️ Currently active tools:

    Node: v16.14.1 (current @ /home/vagrant/proj_a/package.json)
    Tool binaries available: NONE

See options for more detailed reports by running `volta list --help`.
$ cd ..

$ cd proj_b
$ volta pin node@16.14.2
success: pinned node@16.14.2 (with npm@8.5.0) in package.json
$ volta list
⚡️ Currently active tools:

    Node: v16.14.2 (current @ /home/vagrant/proj_b/package.json)
    Tool binaries available: NONE

See options for more detailed reports by running `volta list --help`.
$ cd ..

グローバルにパッケージをインストールする。今回はtypescriptパッケージでやってみる。

$ npm install -g typescript
$ tsc -v
Version 4.6.2

この時点での、proj_a/bそれぞれでtypescriptパッケージをどう見ているか?を見てみる。

$ cd proj_a
$ tsc -v
Version 4.6.2
$ volta list
⚡️ Currently active tools:

    Node: v16.14.1 (current @ /home/vagrant/proj_a/package.json)
    Tool binaries available:
        tsc, tsserver (default)

See options for more detailed reports by running `volta list --help`.
$ cd ..

$ cd  proj_b
$ tsc -v
Version 4.6.2
$ volta list
⚡️ Currently active tools:

    Node: v16.14.2 (current @ /home/vagrant/proj_b/package.json)
    Tool binaries available:
        tsc, tsserver (default)

See options for more detailed reports by running `volta list --help`.
$ cd ..

前回と同じ。どちらのプロジェクトからもグローバルにインストールされたtypescriptパッケージが見えている。

では、プロジェクトのローカルにtypescriptの別バージョンをインストールしてみる。

$ cd proj_a
$ npm install typescript@4.1.5
$ tsc -v
Version 4.1.5
$ volta list
⚡️ Currently active tools:

    Node: v16.14.1 (current @ /home/vagrant/proj_a/package.json)
    Tool binaries available:
        tsc, tsserver (current @ /home/vagrant/proj_a/package.json)

See options for more detailed reports by running `volta list --help`.
$ cd ..

$ cd proj_b
$ npm install typescript@3.9.4
$ tsc -v
Version 3.9.4
$ volta list
⚡️ Currently active tools:

    Node: v16.14.2 (current @ /home/vagrant/proj_b/package.json)
    Tool binaries available:
        tsc, tsserver (current @ /home/vagrant/proj_b/package.json)

See options for more detailed reports by running `volta list --help`.

ちゃんとプロジェクトごとにバージョンが違うtypescriptパッケージが読まれていて、そしてnpxをつけなくてもちゃんとnode_modules内のバイナリを見てくれているのがわかる。

プロジェクトディレクトリ外はどうなっているかというと、

$ cd ..
$ tsc -v
Version 4.6.2
$ volta list
⚡️ Currently active tools:

    Node: v17.7.2 (default)
    Tool binaries available:
        tsc, tsserver (default)

See options for more detailed reports by running `volta list --help`.

こちらはデフォルトのtypescriptパッケージが呼び出されている。やっと想定通りになった。

まとめ

これなら安全に使えそうだし、いちいちnpxやpackage.jsonにscripts追加しなくていいので、かなり楽に使えそうです

nvmからVoltaに変えてみたけど頓挫した・・・

f:id:kun432:20220319174947p:plain

Node.jsのバージョン管理に nodebrew、nvmと使ってきて、複数のバージョンを切り替えれるのはとても便利だけど、同じバージョンを複数の環境で分けたいというニッチな願望がちょっとだけあります。

  • 基本的にCLIのようなものでもグローバルにパッケージインストールしたくないので、プロジェクトのローカルにインストール、呼び出す場合はnpxを使う。
  • が、まれにnpxに対応していないものがある。package.jsonにscripts定義するのはちょっと面倒。グローバルにインストールせざるを得ない。

で、nodebrewやnvmでこういうことができるのか、というと、

  • nodebrewだとエイリアスを使えばできそうな気がしたけど、あくまでもバージョンの別名扱い
  • nvmも対応はしておらず、ここにあるようなワークアラウンド扱い

ということで、どちらもそうじゃない感がある。

いっそdockerでやってしまえなのかもと思いつつ、ちょっと別のバージョン管理ツールを試してみようということで、Voltaを試してみた。

目次

nvmのアンインストール

これだけで良いらしい。一応、.bashrcとか.bash_profileの記述も消した。

$ rm -rf ~/.nvm

Voltaのインストール

Homebrewでかんたん

$ brew install volta
$ volta --version
Updating your Volta directory. This may take a few moments...
1.0.5

ここちょっとハマったんだけど、Homebrewでインストールする場合はこれが必要みたい。

$ volta setup
Updating your Volta directory. This may take a few moments...
success: Setup complete. Open a new terminal to start using Volta!

.bashrcとかに以下のエントリが追加されてる。

export VOLTA_HOME="$HOME/.volta"
export PATH="$VOLTA_HOME/bin:$PATH"

これでOK。ターミナルを立ち上げ直しておく。

VoltaでNode.jsを管理してみる

volta install nodeで最新版のNode.jsを入れる。最新版のLTSが入るみたい。

$ volta install node
success: installed and set node@16.14.2 (with npm@8.5.0) as default

$ node -v
v16.14.2

node@〜でバージョン指定もできる。メジャーバージョンだけ指定するとその最新。細かく指定することも可能。下の方にある通り、volta installでは常にデフォルトが変更されるみたいで、これを変更するサブコマンドはちょっと見当たらない

$ volta install node@14
success: installed and set node@14.19.1 (with npm@6.14.16) as default

$ volta install node@14.19.0
success: installed and set node@14.19.0 (with npm@6.14.16) as default

$ node -v
v14.19.0

@latestだとLTSじゃない最新版になる。

$ volta install node@latest
success: installed and set node@17.7.2 (with npm@8.5.2) as default

volta listで現在使用しているバージョン、volta list allでインストールされているすべてのバージョンが表示される。

$ volta list
⚡️ Currently active tools:

    Node: v17.7.2 (default)
    Tool binaries available: NONE

See options for more detailed reports by running `volta list --help`.

$ volta list all
⚡️ User toolchain:

    Node runtimes:
        v14.19.0
        v14.19.1
        v16.14.2
        v17.7.2 (default)

    Package managers:


    Packages:

プロジェクト(ディレクトリ)ごとにバージョンを固定する場合はvolta pinを使う。

$ mkdir a && cd a
$ volta pin node@16
error: Not in a node package.

Use `volta install` to select a default version of a tool.

ん?と思ったけど、volta pinはpackage.jsonにバージョンを記載するので、そもそもnpm initしてないと使えない。

$ npm init -y
Wrote to /path-in-somewhere/a/package.json:

{
  "name": "a",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

$ volta pin node@16
success: pinned node@16.14.2 (with npm@8.5.0) in package.json

package.jsonを見てみる

$ cat package.json
{
  "name": "a",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "volta": {
    "node": "16.14.2"
  }
}

voltaの記述が増えている。プロジェクト内とプロジェクト外でのNode.jsのバージョンはどうなっているか?

$ pwd
/path-in-somewhere/a

$ node -v
v16.14.2

$ cd ..
$ pwd
/path-in-somewhere

$ node -v
v17.7.2

うん、ちゃんと分かれてる。では、別のプロジェクトを追加して、グローバルインストールしたパッケージがどうなるか見てみる。

$ mkdir b && cd b
$ npm init -y
$ volta pin node@16
$ npm install -g ask-cli
$ ask --version
2.26.0

$ cd ../a
$ pwd
/path-in-somewhere/a
$ ask --version
2.26.0

$ cd ..
$ ask --version
2.26.0

だめじゃん。かつ、プロジェクト外のデフォルトでも読み込めてる。

ドキュメントによると、

With Volta, installing a command-line tool globally with your package manager also adds it to your toolchain. For example, the vuepress package includes an executable of the same name:

yarn global add vuepress

When you install a package to your toolchain, Volta takes your current default Node version and pins the tool to that engine (see Package Binaries for more information). Volta won’t change the tool’s pinned engine unless you update the tool, no matter what. This way, you can be confident that your installed tools don’t change behind your back.

Understanding Volta | Volta

グローバルにインストールしたものはどうやらデフォルトのバージョンに紐づく様子。プロジェクト内・外で見比べてみると、

$ cd a
$ volta list
⚡️ Currently active tools:

    Node: v16.14.2 (current @ /path-in-somewhere/a/package.json)
    Tool binaries available:
        ask (default)
See options for more detailed reports by running `volta list --help`.

$  cd ..
$ volta list
⚡️ Currently active tools:

    Node: v17.7.2 (default)
    Tool binaries available:
        ask (default)

See options for more detailed reports by running `volta list --help`.

どうやらnpmパッケージもvoltaで管理がされる様子。なんだけど、両方ともdefaultになってるんだよな。ということであればpinすればいいのか?と思いきや、pinできるのはnodeとyarnだけらしい・・・

$ volta pin ask-cli@2.25.0
error: Only node and yarn can be pinned in a project

Use `npm install` or `yarn add` to select a version of ask-cli for this project.

ドキュメントにはこうもある。

The node and package manager executables aren’t the only smart tools in your toolchain: the package binaries in your toolchain are also aware of your current directory, and respect the configuration of the project you’re in.

For example, installing the Typescript package will add the compiler executable—tsc— to your toolchain:

npm install --global typescript

Depending on the project you’re in, this executable will switch to the project’s chosen version of TypeScript:

cd /path/to/project-using-typescript-3.9.4
tsc --version # 3.9.4

cd /path/to/project-using-typescript-4.1.5
tsc --version # 4.1.5

うーん、これを見る限りはできるはずなんだけど、どうすればこれが実現できるんだろう・・・

やっぱり、nodeenv使うべしなのかなー・・・

2022/3/21追記:

こちらでできました。

kun432.hatenablog.com