i am currently working on automated unit tests inside the Microsoft Bot Framework 4. From there, i want to check simple conversational statements from the bot. In the CoreBot Tests sample (https://learn.microsoft.com/en-us/azure/bot-service/unit-test-bots?view=azure-bot-service-4.0&tabs=csharp) is demonstrated how it is possible to do that but for me, my bot isnt using dialogs (as far as i know).
My question here is now, how can i unit test simple Question/Answer Statements? My main goal is to unit test my QnA Maker Knowledge Bases.
// 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);
}
}
}
}
The bot i have build is based completly on the NLP with Dispatch sample (https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/csharp_dotnetcore/14.nlp-with-dispatch).
I have already modified the bot a bit and thats why i added the main file (Dispatchbot.cs file in the GitHub repo) for my version.
I can't help you with the syntax for C#, but I have some tests in my nodejs bot that are not dialogs and this may help you. Essentially, you just create a TestAdapter and pass that with an activity to your bot. For example, here is part of my dispatchBot.test.js file:
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);
});
});
If your bot responds with multiple activities, you just need to continue to use testAdapter.activityBuffer.shift()
, even if just to clear the buffer. Otherwise it would keep those messages in the buffer for subsequent tests. Hopefully this will help!