Clova Extensions Kit SDK for Node.jsのテストフレームワークを作ろう

# はじめに

先日LINE BOOT AWARDS 2018 ハッカソン東京が開催されましたね。
都合がつかず参加が出来なかった為、twitterで雰囲気を味わいながら一人でもくもくしてました。

何作ろうかなぁ〜と悩んだ結果、Clova Simulatorを作った方の記事に触発され、
[ASK SDK v2 for Node.jsのテストフレームワークが欲しい（作ってみた）](https://qiita.com/daisukeArk/items/d8aa2c38d33c1670f4b8)の時と同じようにClova版も作ってしまおうと決めました。

テストフレームワークが無くても実機でテストを行えば良い話なのですが、
[以前の投稿](https://qiita.com/daisukeArk/items/afa12708810a6b0b0fde)で書いたようにローカルで気軽にテストがしたいんです！

というわけで前と一緒ですが以下のような理由（一番の理由は2つめ）で自分で作ってみました！
もちろん実装はTypeScriptです！

1. ローカルで気軽にテストがしたい
1. とりあえずOSS開発してみたい

# テストフレームワークの作成

## アサーションについて

Clovaの発話情報は、以下の３つがあります。

* SimpleSpeech(単文タイプ)
    * 「こんにちは」など１文を返すタイプ
* SpeechList(複文タイプ)
    * 「こんにちは」に続けて、効果音を流すなど　音声＋URL(音声ファイル)を返すタイプ
* SpeechSet(複合タイプ)
    * 「挨拶の種類」に続けて、「挨拶にはおはようございます、こんにちは、などがあります。」など　概要＋詳細を返すタイプ

作成したフレームワークでは、以下の機能を提供しています。
~~時間足りなかったので、随時アップデートしていきます！~~
9/5 update!

関数名 | 概要
:-- | :--
equal(expectedValue: string) | SimpleSpeech テキストが完全一致すること
equalList(expectedValues: string[]) | SpeechList テキストが完全一致すること
equalSetBrief(epectedValue: string) | SpeechSet Brief テキストが完全一致すること
equalSetVerbose(epectedValue: string) | SpeechSet Verbose テキストが完全一致すること
equalSetVerboseList(expectedValues: string[]) | SpeechSet Verbose(SpeechList) テキストが完全一致すること

# テストサンプル

サンプルは、[こちら](https://github.com/daisukeArk/clova-conversation-model-assert/blob/master/test/hello-world.test.ts)
呼出したインテントに合わせて結果を確認するようにメソッドチェインしていきます。
まず`init`を呼出し、初期化します。

## 前提条件

* `Clovaハンドラ`と`Expressアプリケーション作成`の部分は分けて実装してください。
* clova-conversation-model-assertでは、`SkillConfigurator.handle()`の戻り値オブジェクトを利用します。

## 初期化時の条件

```typescript
export interface IConversationCondition {
  handler: any;
  request?: {
    locale?: string;
  };
  extensionId?: string;
  description: string;
  user?: {
    userId: string;
    accessToken?: string;
  };
  device?: {
    deviceId: string;
  };
}
```

項目 | 説明 | 備考 |
:-- | :-- | :-- |
handler | Clovaハンドラ | 必須 |
request.locale | ロケール設定(未実装） | |
extensionId | Extension ID | |
description | テストの概要を入力 | 必須 |
user | ユーザ情報 | |
device | デバイス情報 | |

## `requestIntent`メソッドの引数

`requestIntent`メソッドの引数は、

```typescript
export interface IConversation {
    requestIntent(intentName: string, reqCondition?: IRequestCondition): this;
    // 省略
}
```

項目 | 説明
:-- | :--
intentName | 呼び出すインテント名を指定
reqCondition | インテント呼出の条件（後述）

```typescript:IRequestCondition.ts
import * as CEK from '@line/clova-cek-sdk-nodejs';

export interface IRequestCondition {
  intent: {
    slots?: {
      [key: string]: {
        name: string;
        value: CEK.Clova.SlotValue;
        valueType?: CEK.Clova.SlotValueType;
      };
    };
  };
}
```

渡された条件を基に`Request`オブジェクトの中身を設定します。

スロットを指定する場合は例えば以下のように設定します。

```typescript
  .requestIntent('HelloWorldIntent', {
    intent: {
      slots: {
        userName: {
          name: 'userName',
          value: 'daisukeArk'
        }
      }
    }
  })
```

# テスト実行

```bash
$(npm bin)/jest

 PASS  test/hello-world.test.ts
  テストフレームワーク テスト
    LaunchRequest
      ✓ equal SimpleSpeech: ようこそ (2ms)
    HelloWorldIntent
      ✓ equal SimpleSpeech: こんにちは、元気ですか？
      ✓ equal SimpleSpeech: こんにちは、元気ですか？
    Clova.YesIntent
      ✓ equal SimpleSpeech: 今日も一日頑張っていきましょう。
    Clova.NoIntent
      ✓ equal SimpleSpeech: 元気になーれ
    HelloWorldListIntent
      ✓ equal SimpleSpeech: こんにちは、元気ですか？
    HelloWorldSetIntent
      ✓ equal SimpleSpeech: こんにちは
    NotFoundIntent
      ✓ equal SimpleSpeech: もう一度大きな声であいさつをしましょう。 (1ms)
```

サンプルを実行するとこんな感じで表示されます。

# 公開

ここは以前の投稿と重複するので割愛します。

# まとめ

なんとなくハッカソン気分で急いで作ったので至らない点もあるかと思いますがお許しください。。。
随時アップデートしていきます！

不具合や機能追加などご意見あればissueください！

npm
https://www.npmjs.com/package/@daisukeark/clova-conversation-model-assert

github
https://github.com/daisukeArk/clova-conversation-model-assert