過去記事
3 本文
前回までで割といい感じにユニットテストが出来るようになりました。 今回は、Bot Frameworkでユニットテスト的に一番いけてないものをテストしてみたいと思います。
PromptDialogクラス
選択肢をユーザーに表示したりするPromptDialogがあります。 SimpleDialogクラスをちょろっと書き換えて、このPromptDialogを使うようにしてみました。
using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Dialogs.Internals; using Microsoft.Bot.Connector; using System; using System.Threading.Tasks; namespace DITestBotApp.Dialogs { publicinterface ISimpleDialog : IDialog<object> { // こいつもテストするならHelloWorldAsyncとか定義することになるかな } [Serializable] publicclass SimpleDialog : ISimpleDialog { public async Task StartAsync(IDialogContext context) { await context.PostAsync("SimpleDialog started"); PromptDialog.Choice( context, this.HelloWorldAsync, new[] { "A", "B", "C", }, "あなたの好きなのは??"); } private async Task HelloWorldAsync(IDialogContext context, IAwaitable<string> result) { var input = await result; await context.PostAsync($"Hello world!! {input}"); context.Done<object>(null); } } }
こうすることで、以下のような動きのボットが出来上がります。お手軽ですね。
staticメソッドの塊…
PromptDialogは、staticなファクトリメソッドの塊になってます。 そう、前回言いましたがstaticメソッドはモックに差し替えることが出来ない。ユニットテスト的に見たら悪です。 こいつを使われた時点でお手上げ状態になってしまいますね…。
お金の力Visual Studio 2017 Enterprise Edition
ところが、staticメソッドもテスト時に差し替える機能が実はあったります。 Microsoft Fakesがそれです。お高いEnterprise Editionにしかついてない機能なのでなじみのない人が多いかと思いますが、これはかなり強いです。
ということで、Bot Frameworkのアセンブリを右クリックしてFakes アセンブリに追加します。
こうすることでスタブクラスなどを自動で生成してくれます。素敵!でもスタブクラスは正直Moqで作ったのでいいです。Verifyとかで呼び出し確認できる機能はMicrosoft Fakesには確かないので。 問題は、もう1つの機能であるShimです。これはstaticメソッドだろうがなんだろうがテスト時に差し替えてしまいます。
やってみましょう。
using DITestBotApp.Dialogs; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Dialogs.Fakes; using Microsoft.Bot.Connector; using Microsoft.QualityTools.Testing.Fakes; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace DITestBotApp.Test { [TestClass] publicclass SimpleDialogTest { [TestMethod] public async Task StartAsyncTest() { using (ShimsContext.Create()) { var choiceCalled = false; ShimPromptDialog.ChoiceOf1IDialogContextResumeAfterOfM0IEnumerableOfM0StringStringInt32PromptStyleIEnumerableOfString<string>( (context, resumeAfter, chioce, primpt, _, __, ___, ____) => { Assert.AreEqual("A", chioce.ElementAtOrDefault(0)); Assert.AreEqual("B", chioce.ElementAtOrDefault(1)); Assert.AreEqual("C", chioce.ElementAtOrDefault(2)); Assert.AreEqual("あなたの好きなのは??", primpt); choiceCalled = true; }); var dialogContext = new Mock<IDialogContext>(); dialogContext.Setup(x => x.PostAsync(It.Is<IMessageActivity>(y => y.Text == "SimpleDialog started"), default(CancellationToken))) .Returns(Task.CompletedTask) .Verifiable(); dialogContext.Setup(x => x.MakeMessage()).Returns(Activity.CreateMessageActivity()); var target = new SimpleDialog(); await target.StartAsync(dialogContext.Object); Assert.IsTrue(choiceCalled); } } } }
ShomsContext.Create()でusingでくくってしまえば、とりあえずprivateだろうがstaticだろうが何でも使える感じになってます。
実行してみましょう。ばっちり青いです。
Enterprise Eiditon以外でのアプローチ
Enterprise Eiditonを買ってもらうようにアプローチをすればいいのか?