Bot Builder v4 でのテスト : 単純なダイアログとステート管理のテスト

今回は単純なダイアログとステート管理を使うボットのユニットテストを見ていきます。
ダイアログとステート管理については [Bot Builder v4 でボット開発 : 単純なダイアログとステート管理](https://qiita.com/kenakamu/items/5462e4dc08518f1d6c14) を参照してください。

# ダイアログのユニットテスト

通常のユニットテストはメソッド単位でテストを実装しますが、会話サービスの場合は一連の流れでテストをします。これはステートを作成することが大変であることもありますが、ユーザーインプットを含めた一連の流れがメソッドのスコープに近いという事もあります。

もしダイアログが長い場合は、適切な単位にダイアログを分割することも検討してください。

## ステート管理の実装

ダイアログを使う場合はステート管理として DialogState を保存する必要があり、アクセサーを利用します。ユニットテスト時には外部依存をなくすために、MemoryStorage を作成して利用します。

# ソリューションの準備

ボットのコードは [Bot Builder v4 でボット開発 : 単純なダイアログとステート管理](https://qiita.com/kenakamu/items/5462e4dc08518f1d6c14) で開発したものを使います。また前回の記事でユニットテストができる状態まで開発したので、その状態と上記コードをマージして開発のスタート地点とします。コードはそれぞれ article4 および test-article2 ブランチにあります。

1\. 任意のフォルダでレポジトリをクローン。

```shell
git clone https://github.com/kenakamu/botbuilderv4completeguide/
cd botbuilderv4completeguide
```

2\. 以下のコマンドで article4 をチェックアウトした後、test-article2 をチェックアウトしてどちらもローカルにコピー。

```shell
git checkout article4
git checkout test-article2
```

3\. 以下コマンドで test-article3 ブランチを作成。

```shell
git checkout -b test-article3
```
4\. articl4 のブランチをマージ。

```
git merge article4
```

5\. 以下の結果より myfirstbot.csproj、Startup.cs、MyBot.cs は myfirstbot フォルダにマージされ、 MyStateAccessors.cs はルートにコピーされたことを確認。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/28296064-1bef-eefa-4239-2dc246f45516.png)

6\. 以下コマンドで MyStateAccessors.cs を myfirstbot フォルダに移動。

```shell
move MyStateAccessors.cs myfirstbot
```

7\. myfirstbot.sln を Visual Studio で開き、ソリューションをビルド。ビルドに失敗することを確認。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/dc858798-6656-dddd-6c58-b005c8ac40c1.png)

今回のボットはステート管理のためにアクセサーを MyBot クラスのコンストラクタで受け取ることを期待しているため、この点を直していきます。

# テストの実装と実行

はじめに説明した通り、ステート管理のストレージには MemoryStorage を使います。

1\. MyBotUniteTest.cs の MyBot_ShouldReturnSameText メソッドを MyBot_ShouldAskName に変更し、以下のコードを追加します。

```csharp
[TestMethod]
public async Task MyBot_ShouldAskName()
{
    // アダプターを作成
    var adapter = new TestAdapter();
    // ストレージとしてインメモリを利用
    IStorage dataStore = new MemoryStorage();
    // それぞれのステートを作成
    var conversationState = new ConversationState(dataStore);
    var accessors = new MyStateAccessors(conversationState)
    {
        // DialogState を ConversationState のプロパティとして設定
        ConversationDialogState = conversationState.CreateProperty<DialogState>("DialogState"),
    };
    // テスト対象のクラスをインスタンス化
    var bot = new MyBot(accessors);
    // テストの追加と実行
    await new TestFlow(adapter, bot.OnTurnAsync)
        .Test("foo", "名前を入力してください")
        .StartTestAsync();
}
```

2\. ソリューションをビルド後、MyBot_ShouldAskName テストを実行して、成功することを確認。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/87aa27b6-2acc-20a4-635c-f107ab40892e.png)

3\. ダイアログ全体のテストをするため、メソッドを以下の様に変更し、名前を渡すところまでテストを追加。

```csharp
[TestMethod]
public async Task MyBot_ShouldAskName()
{
    // アダプターを作成
    var adapter = new TestAdapter();
    // ストレージとしてインメモリを利用
    IStorage dataStore = new MemoryStorage();
    // それぞれのステートを作成
    var conversationState = new ConversationState(dataStore);
    var accessors = new MyStateAccessors(conversationState)
    {
        // DialogState を ConversationState のプロパティとして設定
        ConversationDialogState = conversationState.CreateProperty<DialogState>("DialogState"),
    };
    // テスト対象のクラスをインスタンス化
    var bot = new MyBot(accessors);
    // テストの追加と実行
    await new TestFlow(adapter, bot.OnTurnAsync)
        .Test("foo", "名前を入力してください")
        .Test("Ken", "ようこそ 'Ken' さん！")
        .StartTestAsync();
}
```

4\. 再度 MyBot_ShouldAskName テストを実行して、成功することを確認。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/81f665ba-378d-92fa-baf9-17739807636e.png)

# まとめ

ダイアログのユニットテストは、テストの単位でどうするか検討が必要ですが、ダイアログのサイズを適切に設計していれば、テストのサイズやパターンも管理できる範囲に収まるはずです。今回は一番シンプルなダイアログとステート管理を使ったボットのユニットテストをしましたが、次回はより高度なダイアログを持つボットのユニットテストをしていきます。

[次の記事へ](https://qiita.com/kenakamu/items/e75ed08d89e94813190b)
[目次へ戻る](https://qiita.com/kenakamu/items/6dc043cfc1f199032883)

[この記事のサンプルコード](https://github.com/kenakamu/botbuilderv4completeguide/tree/test-article3)