Bot Builder v4 でのテスト : ダイアログにおけるリトライの処理と検証のテスト

今回はダイアログにおけるリトライの処理と値の検証に関するテストを見ていきます。リトライの処理と値の検証ついては [Bot Builder v4 でボット開発 : ダイアログにおけるリトライの処理と値の検証](https://qiita.com/kenakamu/items/01ab7f0332eb8138d5f7) を参照してください。

# ソリューションの準備
ボットのコードは  [Bot Builder v4 でボット開発 : ダイアログにおけるリトライの処理と値の検証](https://qiita.com/kenakamu/items/01ab7f0332eb8138d5f7) で開発したものを使うので、コードの詳細はそちらの記事を参照してください。また前回の記事で開発した test-article9 のコードをベースに、article11 ブランチのコードをマージしてテストを開発します。

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

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

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

```shell
git checkout article11
git checkout test-article9
```

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

```shell
git checkout -b test-article10
```

4\. article11 のブランチをマージ。

```shell
git merge article11
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/136b3a18-bd96-a940-cb2a-8fbd620be2eb.png)

5\. マージの競合があるため、以下コマンドで競合を確認。

```shell
git mergetool
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/e062071d-80ad-660f-eb2d-4b51203d3f83.png)

6\. マージのツールを起動しようとするので既定のまま Enter キーを押下。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/20393ce4-f68a-2818-295f-c2bf7ebcde2c.png)

7\. Target 側のコードを選択して、「マージの許可」をクリック。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/806aa65e-1f5c-c588-239b-6f0d30a17fb1.png)

8\. コマンドプロンプトに戻り、変更をコミット。

```shell
git commit -am "merge article11"
```
9\. ルートに作成された Models と Validators フォルダを myfirstbot に移動して、myfirstbot.sln を起動。ソリューションをビルド。エラーが出ないことを確認。

10\. 既存のテストを全て実行。問題がないことを確認。

# ダイアログのリトライ処理のテスト

ダイアログのリトライ処理のテストについては、以下の点に注意します。

- 期待した通りリトライが発生するかはフレームワークの責任範囲のため、テストはしない
- リトライ時に指定した文言が出るは自分のコードのためテストする
- 複数のバリエーションでテストをする

## プロファイルダイアログの AgeStepAsync のテスト

AgeStepAsync では以下のコードをテストします。

- リトライ時のメッセージが ```$"{numberRange.MinValue}-{numberRange.MaxValue} の数字で入力してください。"``` となるか
- 渡す値が範囲外の数値またはそもそも数値ではないものをそれぞれテスト

1\. ProfileDialogUnitTest.cs　に以下のテストメソッドを追加します。

- DataRow を使って複数の値で検証
- 数値に変換できる値とできない値の両方を検証

```csharp
[TestMethod]
[DataRow("-1")]
[DataRow("130")]
[DataRow("fourty")]
public async Task ProfileDialog_ShouldAskRetryWhenAgeOutOfRange(string age)
{
    // テスト用の変数
    var name = "Ken";
    var numberRange = new NumberRange() { MinValue = 0, MaxValue = 120 };

    // テストの追加と実行
    await ArrangeTestFlow()
    .Test("foo", "名前を入力してください。")
    .Test(name, "年齢を聞いてもいいですか？ (1) はい または (2) いいえ")
    .Test("はい", "年齢を入力してください。")
    .Test(age, $"{numberRange.MinValue}-{numberRange.MaxValue} の数字で入力してください。")
    .StartTestAsync();
}
```
2\. 変更を保存してテストの結果を確認。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/3c5a4291-7549-152e-5346-f6c94b35bc51.png)

# 値の検証のテスト

あたしの検証コードは上記ダイアログ内でカバーされるが、個別にもテストが可能なため、テストを作ります。

1\. ユニットテストプロジェクトに NumberValidatorsUnitTest.cs を追加して以下のコードと差し替え。

- 検証に必要な PromptValidatorContext<T> はコンストラクタが必要だが internal コンストラクタのため、リフレクションを使ってコンストラクタを取得、インスタンスの作成
- DataRow を使った複数値のテストをサポート

```csharp:NumberValidatorsUnitTest.cs
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace myfirstbot.unittest
{
    [TestClass]
    public class NumberValidatorsUnitTest
    {       

        private PromptValidatorContext<T> CreatePromptValidatorContext<T>(bool succeeded, T obj, object validations)
        {
            // リフレクションでコンストラクターを取得して実行し、PromptValidatorContext を作成
            var promptContext = (PromptValidatorContext<T>)typeof(PromptValidatorContext<T>).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null,
                new[] {
                    typeof(ITurnContext),
                    typeof(PromptRecognizerResult<T>),
                    typeof(IDictionary<string, object>),
                    typeof(PromptOptions)
                }, null)
                .Invoke(new object[]
                {
                    null,
                    new PromptRecognizerResult<T>() { Succeeded = succeeded, Value = obj },
                    null,
                    new PromptOptions(){ Validations = validations }
                });

            return promptContext;
        }

        [TestMethod]
        [DataRow(1)]
        [DataRow(50)]
        [DataRow(120)]
        public async Task NumberValidators_ShouldReturnTrueWithValidNumber(int input)
        {
            var promptContext = CreatePromptValidatorContext<int>(true, input, new NumberRange() { MinValue = 0, MaxValue = 120 });
            var result = await NumberValidators.ValidateRangeAsync(promptContext, CancellationToken.None);
            Assert.IsTrue(result);
        }

        [TestMethod]
        [DataRow(-1)]
        [DataRow(121)]
        public async Task NumberValidators_ShouldReturnFalseWithInalidNumber(int input)
        {
            var promptContext = CreatePromptValidatorContext<int>(true, input, new NumberRange() { MinValue = 0, MaxValue = 120 });
            var result = await NumberValidators.ValidateRangeAsync(promptContext, CancellationToken.None);
            Assert.IsFalse(result);
        }

        [TestMethod]
        public async Task NumberValidators_ShouldReturnFalseWithFailedResult()
        {
            var promptContext = CreatePromptValidatorContext<int>(false, 0, new NumberRange() { MinValue = 0, MaxValue = 120 });
            var result = await NumberValidators.ValidateRangeAsync(promptContext, CancellationToken.None);
            Assert.IsFalse(result);
        }
    }
}
```

2\. テストの実行とテストの確認。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/590d3be0-d76d-71b6-bbfa-a7a24298f0d3.png)

# まとめ

今回はダイアログの高度な機能であるリトライ処理を検証を見ていきました。次回もダイアログの他の機能を見ていきます。

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

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