【问题标题】:How can i unit test my bot without dialogs? (c#)如何在没有对话框的情况下对我的机器人进行单元测试? (C#)
【发布时间】:2021-06-14 06:34:10
【问题描述】:

我目前正在 Microsoft Bot Framework 4 中进行自动化单元测试。 从那里,我想检查来自机器人的简单对话语句。在核心机器人中 测试样本 (https://docs.microsoft.com/en-us/azure/bot-service/unit-test-bots?view=azure-bot-service-4.0&tabs=csharp) 演示了如何做到这一点,但对我来说,我的机器人不使用对话框(据我所知)。

我现在的问题是,我如何对简单的问题/答案语句进行单元测试? 我的主要目标是对我的 QnA Maker 知识库进行单元测试。

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using System;
using Newtonsoft.Json;
using System.IO;
using Microsoft.Bot.Builder.AI.QnA;

using Microsoft.Extensions.Configuration;

namespace SEKAI.Bots
{
    public class DispatchBot : ActivityHandler
    {
        private ILogger<DispatchBot> _logger;
        private IBotServices _botServices;
        private IConfiguration _configuration;

        public DispatchBot(IConfiguration configuration, IBotServices botServices, ILogger<DispatchBot> logger)
        {
            _configuration = configuration;
            _logger = logger;
            _botServices = botServices;
        }

        protected async Task NoMatchFound(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            var table = BotUtility.BotUtility.GetTableReference(_configuration);
            BotUtility.BotUtility.InsertRecord(turnContext, table);

            // Wird ausgeführt, wenn keine KnowledgeBase gefunden wird
            System.Diagnostics.Debug.WriteLine("### FINDE KEINE PASSENDE ANTWORT ###");
            await turnContext.SendActivityAsync(MessageFactory.Text("Leider kann ich Ihnen hierbei noch nicht weiterhelfen. Ich bin aber schon dabei, Neues zu lernen!"), cancellationToken);
        }

        // Wenn der Benutzer den Chat startet
        protected override async Task OnEventActivityAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
        {
            if (turnContext.Activity.Name == "webchat/join")
            {
                string WelcomeCardpath = Path.Combine(".", "AdaptiveCards", "WelcomeCard.json");
                var cardAttachment = BotUtility.BotUtility.CreateAdaptiveCard(WelcomeCardpath);
                await turnContext.SendActivityAsync(MessageFactory.Attachment(cardAttachment), cancellationToken);
            }
        }

        // Wenn ein Nutzer eine Nachricht schreibt
        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            // First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
            var recognizerResult = await _botServices.Dispatch.RecognizeAsync(turnContext, cancellationToken);

            // Top intent tell us which cognitive service to use.
            var topIntent = recognizerResult.GetTopScoringIntent();

            // Next, we call the dispatcher with the top intent.
            await DispatchToTopIntentAsync(turnContext, topIntent.intent, recognizerResult, cancellationToken);
        }

        // Suche nach der richtigen KnowledgeBase
        private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
        {
            switch (intent)
            {
                case "q_SEKAI_Chitchat":
                    await ProcessSEKAI_ChitchatAsync(turnContext, cancellationToken);
                    break;

                default:
                    // Wird ausgeführt, wenn keine KnowledgeBase gefunden wird
                    _logger.LogInformation($"Dispatch unrecognized intent: {intent}.");
                    await NoMatchFound(turnContext, cancellationToken);
                    break;
            }
        }

        // Bearbeitung aus SEKAI_Chitchat
        private async Task ProcessSEKAI_ChitchatAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            _logger.LogInformation("ProcessSEKAI_ChitchatAsync");

            // Confidence Score zur KnowledgeBase
            var metadata = new Metadata();
            var qnaOptions = new QnAMakerOptions();
            qnaOptions.ScoreThreshold = 0.70F;

            // Speichere die Antwort aus der KnowledgeBase
            var results = await _botServices.SEKAI_Chitchat.GetAnswersAsync(turnContext, qnaOptions);
            if (results.Any())
            {
                // Speichere die Antwort aus der KnowledgeBase für die Schlüsselwort-Verarbeitung
                string savetext = results.First().Answer;
                System.Diagnostics.Debug.WriteLine(savetext);

                if (savetext.Contains("{card}"))
                {
                    // Hier wird das Schlüsselwort für die Antwortausgabe entfernt
                    int firstKeyword = savetext.IndexOf("{card}") + "{card}".Length;
                    int lastKeyword = savetext.LastIndexOf("{/card}");
                    string subsavetext = savetext.Substring(firstKeyword, lastKeyword - firstKeyword);
                    System.Diagnostics.Debug.WriteLine(subsavetext);

                    // Ausgabe der Adaptive Card
                    savetext = savetext.Replace("{card}" + subsavetext + "{/card}", "");
                    string AdaptiveCardPath = Path.Combine(".", "AdaptiveCards", subsavetext + ".json");
                    var cardAttachment = BotUtility.BotUtility.CreateAdaptiveCard(AdaptiveCardPath);
                    await turnContext.SendActivityAsync(MessageFactory.Attachment(cardAttachment), cancellationToken);

                    // Ausgabe von Text
                    await turnContext.SendActivityAsync(MessageFactory.Text(savetext), cancellationToken);
                }
                else
                {
                    // Befindet sich in der Datenbank kein Schlüsselwort, wird nur die Antwort ausgegeben
                    await turnContext.SendActivityAsync(MessageFactory.Text(savetext), cancellationToken);
                }
            }
            else
            {
                // Wird ausgegeben, wenn keine Antwort in der KnowledgeBase passt
                await NoMatchFound(turnContext, cancellationToken);
            }
        }
    }
}

我构建的机器人完全基于 NLP 和 Dispatch 示例 (https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/csharp_dotnetcore/14.nlp-with-dispatch)。

我已经稍微修改了机器人,这就是为什么我为我的版本添加了主文件(GitHub 存储库中的 Dispatchbot.cs 文件)。

【问题讨论】:

    标签: c# unit-testing botframework bots


    【解决方案1】:

    我无法帮助您了解 C# 的语法,但我的 nodejs 机器人中有一些不是对话框的测试,这可能会对您有所帮助。本质上,您只需创建一个 TestAdapter 并将其与一个活动一起传递给您的机器人。例如,这是我的 dispatchBot.test.js 文件的一部分:

    const { TestAdapter, ActivityTypes, TurnContext, ConversationState, MemoryStorage, UserState } = require('botbuilder');
    const { DispatchBot } = require('../../bots/dispatchBot');
    const assert = require('assert');
    const nock = require('nock');
    require('dotenv').config({ path: './.env' });
    
    // Tests using mocha framework
    describe('Dispatch Bot Tests', () => {
    
        const testAdapter = new TestAdapter();
        async function processActivity(activity, bot) {
            const context = new TurnContext(testAdapter, activity);
            await bot.run(context);
        }
    
        it('QnA Generic Response'), async () => {
            const memoryStorage = new MemoryStorage();
            let bot = new DispatchBot(new ConversationState(memoryStorage), new UserState(memoryStorage), appInsightsClient);
    
            // Create message activity
            const messageActivity = {
                type: ActivityTypes.Message,
                channelId: 'test',
                conversation: {
                    id: 'someId'
                },
                from: { id: 'theUser' },
                recipient: { id: 'theBot' },
                text: `This is an unrecognized QnA utterance for testing`
            };
    
            // Send the conversation update activity to the bot.
            await processActivity(messageActivity, bot);
    
            // Assert we got the right response
            let reply = testAdapter.activityBuffer.shift();
            assert.equal(reply.text,defaultQnaResponse);
        });
    });
    

    如果您的机器人响应多个活动,您只需继续使用testAdapter.activityBuffer.shift(),即使只是为了清除缓冲区。否则,它会将这些消息保留在缓冲区中以供后续测试。希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-05
      • 2016-11-05
      • 2014-07-29
      • 2022-10-19
      • 1970-01-01
      • 2020-10-23
      • 1970-01-01
      相关资源
      最近更新 更多