Bot Framework と Microsoft Graph で DevOps その 6 : ファンクションテストのセットアップ

※この記事書いてて、前に出した記事のコード間違っていることに気づきました。すでに見てコピペした人は直してください。

前回はビルド定義まで作りました。今回はファンクションテストについて考えます。ちょっと長いです。

Bot Framework でのファンクションテスト

Bot Framework では、Direct Line を使って通しのテストができます。しかし Bot Connector が特定の URL しか見ないので、テスト用のボットアプリも登録する必要があります。Infrastructure as a Code については次回。

Azure App Services へアプリを公開

1. ボットアプリプロジェクトを右クリックして Publish。

image

2. Microsoft Azure App Services を選択し、新規作成。

image

3. まず本番用に作成して、公開。
image

4. 公開が終わったら、Create new profile リンクから、もう 1 つプロファイルを作成。

image

5. テスト用に公開。別に同じリソースグループやサービスプランである必要はありません。

image

ボットアプリの登録

1. https://dev.botframework.com に行って、サインイン。

2. My Bots より Create a bot をクリック。

image

3. Register をクリックし本番用ボットアプリの登録。アドレスは本番用のものを。プロトコル https にするのと、/api/messages をつけ忘れないように。

4. 登録時に作成した Bot Id、Microsoft App Id とMicrosoft App Password をメモ。

5. 登録が完了したら、Direct Line も追加。(地球儀的なアイコン)

image

6. Add new site をクリックし、任意の名前を入れて Done。

image

7. Secret keys のどちらかを Show をクリックしてメモ。Done をクリック。

image

8. 続いてテスト用のボットアプリも登録。同じように Direct Line も登録し、各種キーを保存。

App Service にアプリケーションパラメーター追加

本来 Id や Password は Web.config に登録しますが、複数環境に分けるため、 App Service 側にこれらの値を入れます。

1. https://portal.azure.com にログインします。

2. 作成した本番用の App Service を開きます。

image

3. Application Settings を選択して、App settings に BotId、MicrosoftAppId、MicrosoftAppPassword、ActiveDirectory.RedirectUrl を追加、それぞれの値を入力します。ActiveDirectory.RedirectUrl の値は App Services の Uri に /api/OAuthCallback をつけたものです。

image

4. テスト用も同様に設定します。

Azure AD アプリの更新

1. 以前 Azure AD に登録したアプリを開きます。

2. Reply URLs を開き、今回作った App Service のアドレスを追加します。

image

テストプロジェクトの作成

下準備が完了したので、ソリューションにテストプロジェクトを追加します。

1. Visual Studio でソリューションを右クリックして、プロジェクトを追加。

2. Unit Test Project を選択して追加。名前は O365Bot.FunctionTests としました。

image

3. 以下の NuGet パッケージを追加。

image

4. DirectLine を簡単に使うヘルパーとして DirectLineHelper.cs ファイルを追加し、以下のコードに差し替え。

using Microsoft.Bot.Connector.DirectLine;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;

namespace O365Bot.FunctionTests
{
    public class DirectLineHelper
    {
        private TestContext testContext;

        private string conversationId;
        private string userId;
        private string watermark;

        private DirectLineClient client;

        public DirectLineHelper(TestContext testContext)
        {            
            client = new DirectLineClient(testContext.Properties["DirectLineSecret"].ToString());
            userId = testContext.Properties["UserId"].ToString();
            conversationId = client.Conversations.StartConversation().ConversationId;
            watermark = null;
        }

        public List<Activity> SentMessage(string text)
        {
            Activity activity = new Activity()
            {
                Type = ActivityTypes.Message,
                From = new ChannelAccount(userId, userId),
                Text = text
            };
            client.Conversations.PostActivity(conversationId, activity);
            var reply = client.Conversations.GetActivities(conversationId, watermark);

            watermark = reply.Watermark;
            return reply.Activities.Where(x => x.From.Id != userId).ToList();
        }
    }
}

5. テスト実行時の本場用とテスト用設定ファイルとして、XML File を追加。拡張子を .runsettings とする。

image

image

6. 設定ファイルを以下に差し替え、それぞれの値を設定。

<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
  <TestRunParameters>
    <Parameter name="DirectLineSecret" value="__YourSecret__" />
    <Parameter name="BotId" value="__BotId__" />
    <Parameter name="UserId" value="__UserId__" />
  </TestRunParameters>
</RunSettings>

7. 既定で作成されている UnitTest1.cs を FunctionsTest1.cs にリネームして、以下に差し替え。

using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace O365Bot.FunctionTests
{
    [TestClass]
    public class FunctionTest1
    {
        public TestContext TestContext { get; set; }
        [TestMethod]
        public void Function_ShouldReturnEvents()
        {
            DirectLineHelper helper = new DirectLineHelper(TestContext);
            var toUser = helper.SentMessage("get appointments");
            Assert.IsTrue(toUser.First().Text.Equals("2017-05-31 12:00-2017-05-31 13:00: dummy event"));
        }
    }
}

認証の課題

ここまで順調に来ましたが、ここで AuthBot の認証をどうすればいいかという問題があります。答えは同じ UserId で Direct Line 経由で一度認証してしまう。です。

1. ソリューションのコンソールアプリプロジェクトを追加します。名前は O365Bot.AuthConsole としました。

2. 以下の NuGet パッケージを追加します。

image

3. Reference より System.Configuration を追加します。

image

4. Program.cs を以下のコードに差し替えます。

using Microsoft.Bot.Connector.DirectLine;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace O365Bot.AuthConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var userId = ConfigurationManager.AppSettings["UserId"];
            var directLineSecret = ConfigurationManager.AppSettings["DirectLineSecret"];
            DirectLineClient client = new DirectLineClient(directLineSecret);
            var conversation = client.Conversations.StartConversation();
            Activity activity = new Activity()
            {
                Type = ActivityTypes.Message,
                From = new ChannelAccount(userId, userId),
                Text = "Hi"
            };
            client.Conversations.PostActivity(conversation.ConversationId, activity);
            var reply = client.Conversations.GetActivities(conversation.ConversationId, null);
            var authReply = reply.Activities.Where(x => x.From.Id != userId).First();
            if (authReply.Attachments != null &&
                authReply.Attachments.First().ContentType != "application/vnd.microsoft.card.signin")
                return;

            Console.WriteLine((authReply.Attachments.First().Content as dynamic).buttons[0].value);
            Console.WriteLine("Copy the address, past to browser and authenticate, then past the displayed code back");
            var code = Console.ReadLine();

            activity = new Activity()
            {
                Type = ActivityTypes.Message,
                From = new ChannelAccount(userId, userId),
                Text = code
            };

            client.Conversations.PostActivity(conversation.ConversationId, activity);
            return;
        }
    }
}

5. App.config のconfiguration ノードに以下を追加し、値を変更します。UserId はテストで使うものと同じにしてください。

<appSettings>
  <add key="DirectLineSecret" value="__YourSecret__" />
  <add key="UserId" value="__UserId__" />
</appSettings>

6. コンソールアプリを実行。画面に認証用 URL が出るので、ブラウザに張り付け認証。

image

7. 認証結果のコードを画面に張り付けて Enter。

image

8. 本番、テストともに実行します。

ファンクションテストの実行

1. Visual Studio の Test メニューより Test Settings | Select Test Settings File を選択します。

image

2. テストする方の設定ファイルを選択します。とりあえず Test.runsettings を選択。

image

3. Function_ShouldReturnEvents メソッドの初めにブレークポイント設置。

4. ソリューションをコンパイル後、テストエクスプローラーより Function_ShouldReturnEvents をデバッグ実行。

image

5. 結果が返ってきた時点で確認。コードに設定している Asset とは一致しないので、テストに工夫は必要だが動作していることは確認。

image

6. デバッグを止めて、とりあえずエラーがなければ良いということで、Asset を以下のように変更。ほかに良いのないかな。。

Assert.IsTrue(true);

まとめ

長い道のりでしたが、Function Test のセットアップは完了。次回は VSTS でリリース定義を作ります。