Интеграционный тест с использованием Bot Framework и DirectLine для потоковых случаев.
Введение
В предыдущих сообщениях блога мы провели интеграционный тест для отдельных случаев. «Единичные случаи» — это те случаи, что задав что-то боту, он ответит только один раз, а затем мы сравниваем результаты.
В этом руководстве не объясняется, как выполняются аутентификация и вызовы API. Если вы хотите это проверить, ознакомьтесь с руководством для отдельных случаев.
А как насчет плавных разговоров?
Используя текущий способ, у нас нет какого-либо потока в разговоре, например, если вы хотите попросить о помощи, а затем появляется меню с различными опциями, а затем, после выбора одного из них, другое меню. Это составит примерно 2 или более responses. Это означает, что использование решения для интеграционного тестирования, которое мы использовали ранее, не будет работать.
Вот схема того, как работает интеграционный тест для отдельных случаев.
А вот схема того, как мы собираемся адаптировать текущее решение для интеграционного тестирования к потоковым сценариям.
[
Следующее объяснение не охватывает всю основную информацию о том, как работает Bot Framework. Если вы не понимаете, пожалуйста, просмотрите официальную документацию.
Пример случая
В следующем руководстве я буду использовать бота с созданным мной потоковым диалогом, который просит о помощи, а затем выбирает различные варианты.
[
Новая структура JSON
Теперь, когда у нас есть более одного request при разговоре с ботом, нам нужно изменить нашу структуру json, чтобы добавить все request, которые мы будем делать.
[[[ТОК_9]]]
Как видите, произошло важное изменение: request теперь называется requests. Это означает, что теперь у нас есть List<Activity> вместо одного activity.
Новые объекты
Раньше у нас были объекты, настроенные для отдельных случаев: TestEntry и TestEntryCollection. Для случаев потока мы будем создавать новые объекты: TestEntryFlow и TestEntryFlowCollection.
TestEntryFlow
Этот объект предназначен для каждой записи, которая есть в коллекции. Обратите внимание, что объект Requests теперь является List<Activity> вместо одного Activity, как я упоминал ранее.
Поскольку мы будем спрашивать бота несколько раз, нам нужно иметь несколько activities, которые будут отправлены в беседу.
public class TestEntryFlow
{
/// <summary>
/// Entry name
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Activity requested by the entry
/// </summary>
[JsonProperty("requests")]
public List<Activity> Requests { get; set; }
/// <summary>
/// Activity response expected by the entry
/// </summary>
[JsonProperty("response")]
public Activity Response { get; set; }
/// <summary>
/// Assert value in string
/// </summary>
[JsonProperty("assert")]
public string Assert { get; set; }
}
TestEntiresCollection
Этот объект будет содержать соответствующую информацию для DirectLine, такую как secret и конечные точки, а также список Entries, который мы будем тестировать.
Обратите внимание, что Entries теперь представляет собой список TestEntryFlow, а не TestEntry.
public class TestEntryFlowCollection
{
/// <summary>
/// DirectLine Secret
/// </summary>
[JsonProperty("secret")]
public string Secret { get; set; }
/// <summary>
/// Endpoint to get the token using the secret for DirectLine
/// </summary>
[JsonProperty("directlineGenerateTokenEndpoint")]
public string DirectLineGenerateTokenEndpoint { get; set; }
/// <summary>
/// Endpoint for a conversation in DirectLine
/// </summary>
[JsonProperty("directlineConversationEndpoint")]
public string DirectLineConversationEndpoint { get; set; }
/// <summary>
/// Entries list
/// </summary>
[JsonProperty("entries")]
public List<TestEntryFlow> Entries { get; set; }
}
Создание TestMethod для случаев потока
Новый поток
Прежде всего, еще раз взгляните на схему (она та же, что я выложил выше).
Как видите, структура потока выполнения теста практически такая же:
Получить информацию
Аутентификация
Создать разговорИ вот что изменилось: теперь нам нужно несколько раз отправлять весь запрос в беседу. Для этого нам нужно выполнить цикл для каждого запроса, отправить его боту, а затем сравнить последний ответ с ожидаемым ответом.
Отправьте все запросы
Получить все сообщения
Получите последний ответ
Сравните с ожидаемым ответом
Подтвердить результат
Код
Прежде всего, нам нужно получить информацию из файла, это то же самое, что мы делали раньше с отдельными случаями.
// Load entries from file
var path = System.IO.File.ReadAllText(@"C:\dataFlow.json");
// Deserialize to object
var data = JsonConvert.DeserializeObject<TestEntryFlowCollection>(path);
Теперь нам нужно выполнить цикл для каждого TestEntryFlow из data.entries, при этом мы можем следовать тому же потоку, который мы делали в отдельных случаях, до новой части, где мы зацикливаемся на requests.
/// Arrange with current requested values
string token, newToken, conversationId;
Activity latestResponse = new Activity();
/// Act for step
/// 1 - Get token using secret from DirectLine in BotFramework panel
token = Utils.uploadString<DirectLineAuth>(data.Secret, data.DirectLineGenerateTokenEndpoint, "").token;
/// 2 - Create a new conversation
var createdConversation = Utils.uploadString<DirectLineAuth>(token, data.DirectLineConversationEndpoint, "");
// This returns a new token and a conversationId
newToken = createdConversation.token;
conversationId = createdConversation.conversationId;
/// 3 - Send an activity to the conversation with new token and conversationId
string directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + "/activities";
Следующий шаг довольно прост: нам нужно зациклить entry.requests и отправить каждый activity в диалог.
foreach (Activity step in entry.Requests)
{
if (step.Type == ActivityTypes.Message)
{
/// Step
Utils.uploadString<DirectLineAuth>(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(step));
/// 4 - Get all activities, we get a List<activity> and a watermark
var getLastActivity = Utils.downloadString<ActivityResponse>(newToken, directlineConversationActivitiesEndpoint);
/// 5 - Get the latest activity which is the response we should be expecting
latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
}
}
Мы используем watermark для получения последнего сообщения. watermark — это значение, которое API DirectLine возвращает при запросе информации о разговоре.
После этого нам останется только заполнить globals нашими latestReponse и expectedResponse.
/// Arrange with new values
var globals = new Objects.Globals { Request = entry.Response, Response = latestResponse };
И чтобы закончить дело, мы оцениваем строку assert в entry.
/// Assert
Assert.IsTrue(await CSharpScript.EvaluateAsync<bool>(entry.Assert, globals: globals));
Окончательный код
[TestMethod]
public async Task ShouldTestFlowCases()
{
// Load entries from file
var path = System.IO.File.ReadAllText(@"C:\dataFlow.json");
// Deserialize to object
var data = JsonConvert.DeserializeObject<TestEntryFlowCollection>(path);
/// Flow: Arrange -> Act -> arrange -> assert
foreach (TestEntryFlow entry in data.Entries)
{
/// Arrange with current requested values
string token, newToken, conversationId;
Activity latestResponse = new Activity();
/// Act for step
/// 1 - Get token using secret from DirectLine in BotFramework panel
token = Utils.uploadString<DirectLineAuth>(data.Secret, data.DirectLineGenerateTokenEndpoint, "").token;
/// 2 -Create a new conversation
var createdConversation = Utils.uploadString<DirectLineAuth>(token, data.DirectLineConversationEndpoint, "");
// This returns a new token and a conversationId
newToken = createdConversation.token;
conversationId = createdConversation.conversationId;
/// 3 - Send an activity to the conversation with new token and conversationId
string directlineConversationActivitiesEndpoint = data.DirectLineConversationEndpoint + conversationId + "/activities";
foreach (Activity step in entry.Requests)
{
if (step.Type == ActivityTypes.Message)
{
/// Step
Utils.uploadString<DirectLineAuth>(newToken, directlineConversationActivitiesEndpoint, JsonConvert.SerializeObject(step));
/// 4 - Get all activities, we get a List<activity> and a watermark
var getLastActivity = Utils.downloadString<ActivityResponse>(newToken, directlineConversationActivitiesEndpoint);
/// 5 - Get the latest activity which is the response we should be expecting
latestResponse = getLastActivity.activities[Int32.Parse(getLastActivity.watermark)];
}
}
/// Arrange with new values
var globals = new Objects.Globals { Request = entry.Response, Response = latestResponse };
/// Assert
Assert.IsTrue(await CSharpScript.EvaluateAsync<bool>(entry.Assert, globals: globals));
}
await Task.CompletedTask;
}
Улучшения
Я верю, что лучший поток возможен, но это улучшение будет означать, что структуру JSON также следует изменить. Кроме того, чтобы это произошло, json должен быть более заполнен.
Чтобы сделать это тестирование лучше, у нас должен быть response для каждого request, и мы должны утверждать каждый раз, когда отправляем сообщение. Сейчас мы делаем это, сохраняя все requests и финальный response .
Я сделал схему, чтобы показать, как это будет выглядеть.
Я твердо убежден, что этот способ в целом намного лучше с точки зрения целостности теста, поскольку вы тестируете практически каждое поведение в потоке, а не просто тестируете окончательный ответ.
Ну, это все, что касается этого руководства. Помните, что это руководство является продолжением руководства для отдельных случаев. Если вы чувствуете себя потерянным, обратитесь к тому руководству, которое длиннее и содержит больше объяснений для всего.
Помните, что весь код хранится у меня на github в репозитории this.
Хорошего дня!


