หมายเหตุ
การเข้าถึงหน้านี้ต้องได้รับการอนุญาต คุณสามารถลอง ลงชื่อเข้าใช้หรือเปลี่ยนไดเรกทอรีได้
การเข้าถึงหน้านี้ต้องได้รับการอนุญาต คุณสามารถลองเปลี่ยนไดเรกทอรีได้
การทดสอบหน่วยช่วยให้คุณตรวจสอบการทำงานของเอเจนต์ได้ในแต่ละเทิร์น โดยไม่ต้องปรับใช้กับแชนเนล เชื่อมต่อกับ Azure AI Bot Service หรือส่งทราฟฟิก HTTP จริง คู่มือนี้สร้างตั้งแต่การทดสอบเสียงสะท้อนแบบง่ายไปจนถึงโฟลว์แบบหลายรอบ การจำลองการขึ้นต่อกัน และการตรวจสอบเชิงความหมายที่รองรับด้วย AI
Prerequisites
เพิ่มแพคเกจ Microsoft.Agents.Builder.Testing NuGet ไปยังโครงการทดสอบของคุณ ซึ่งรวมถึง AgentTestHost, TestAdapterและ TestFlow - สามคอมโพเนนต์ที่คุณใช้ในการทดสอบทุกครั้ง
<PackageReference Include="Microsoft.Agents.Builder.Testing" Version="*" />
การทดสอบในคู่มือนี้ใช้ xUnit และ Moq แต่ไลบรารีการทดสอบไม่ขึ้นอยู่กับเฟรมเวิร์กการทดสอบเฉพาะ
แนวคิดหลัก
TestAdapter
TestAdapter เป็นตัวแทนชั่วคราวที่ทำงานอยู่ภายในโพรเซสสำหรับอะแด็ปเตอร์ช่องสัญญาณจริง แทนที่จะส่งกิจกรรมผ่านเครือข่าย ระบบจะจัดคิวการตอบกลับของเอเจนต์ไว้ใน ActiveQueue ซึ่งคำสั่งตรวจสอบของคุณจะอ่านจากตรงนั้น นอกจากนี้ยังมี MockUserTokenClient สำหรับจำลองโฟลว์ OAuth
TestFlow
TestFlow เป็น API คล่องแคล่วสําหรับการสร้างแบบจําลองการสนทนาเป็นลําดับการดําเนินการส่ง/assert การทำงานแบบลูกโซ่นี้จะถูกประมวลผลเมื่อจำเป็นต้องใช้เท่านั้น ไม่มีอะไรดําเนินการจนกว่าคุณจะเรียกStartTestAsync()
await new TestFlow(adapter, myBotCallback)
.Send("hello")
.AssertReply("Hello back!")
.StartTestAsync();
AgentTestHost
AgentTestHost ครอบ IHost ที่กำหนดค่าในลักษณะเดียวกับ Program.cs สำหรับการทำงานจริง โดยจะลงทะเบียน TestAdapter ไว้ล่วงหน้าเป็น IChannelAdapter และเปิดให้เข้าถึง CreateTestFlow() เพื่อเริ่มการสนทนาทดสอบ
await using var host = AgentTestHost.Create(builder =>
{
builder.Services.AddSingleton<IStorage, MemoryStorage>();
builder.Services.AddTransient<IAgent, MyAgent>();
});
await host.CreateTestFlow()
.Send("hello")
.AssertReply("Hello back!")
.StartTestAsync();
สำคัญ: อย่าเรียก
AddAgent<T>()ภายในAgentTestHost.Create()เมธอดนั้นลงทะเบียนCloudAdapterซึ่งขัดแย้งกับTestAdapterที่ลงทะเบียนไว้ล่วงหน้าแล้ว ลงทะเบียนIAgentโดยตรงโดยใช้AddTransient<IAgent, T>()หรือตัวแทนโรงงาน
การทดสอบครั้งแรกของคุณ
กําหนดตัวแทนที่สะท้อนข้อความของผู้ใช้:
public class EchoAgent : AgentApplication
{
public EchoAgent(AgentApplicationOptions options) : base(options)
{
OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last);
}
private async Task OnMessageAsync(ITurnContext tc, ITurnState ts, CancellationToken ct)
{
await tc.SendActivityAsync($"You said: {tc.Activity.Text}", cancellationToken: ct);
}
}
การทดสอบ:
[Fact]
public async Task Echo_ReturnsUserText()
{
await using var host = AgentTestHost.Create(builder =>
{
builder.Services.AddSingleton<IStorage, MemoryStorage>();
builder.Services.AddTransient<IAgent>(sp =>
new EchoAgent(new AgentApplicationOptions(sp.GetRequiredService<IStorage>())));
});
await host.CreateTestFlow()
.Send("hello")
.AssertReply("You said: hello")
.StartTestAsync();
}
แต่ละขั้นตอนทําอะไรได้บ้าง
| ขั้นตอน | Description |
|---|---|
AgentTestHost.Create(...) |
สร้างและเริ่มต้นโฮสต์ภายในโปรเซสโดยมี TestAdapter ที่กำหนดค่าไว้ล่วงหน้า |
CreateTestFlow() |
ดึง IAgent จาก DI และครอบไว้ใน TestFlow |
.Send("hello") |
Messageสร้างกิจกรรมและกําหนดเส้นทางกิจกรรมผ่านตัวแทน |
.AssertReply("You said: hello") |
นำข้อความตอบกลับถัดไปออกจากคิวและยืนยันข้อความ (ถูกตัดออก, ตรงกันทุกประการ) |
.StartTestAsync() |
ประมวลผลคำสั่งในห่วงโซ่ทั้งหมด |
ตัวแปรการยืนยัน
TestFlow มีวิธีการยืนยันหลายวิธีโดยขึ้นอยู่กับความแม่นยําในการตรวจสอบของคุณ
จับคู่ข้อความตรงกันทุกประการ
.AssertReply("You said: hello")
การจับคู่สตริงย่อย
.AssertReplyContains("hello")
หนึ่งในคําตอบที่ยอมรับได้
.AssertReplyOneOf(new[] { "Hi!", "Hello!", "Hey!" })
การตรวจสอบแบบกําหนดเองกับผู้รับมอบสิทธิ์
.AssertReply(activity =>
{
Assert.Equal(ActivityTypes.Message, activity.Type);
Assert.Contains("hello", activity.Text);
})
ผู้รับมอบสิทธิ์การตรวจสอบ Async
.AssertReplySatisfies(async activity =>
{
var data = await LoadExpectedDataAsync();
Assert.Equal(data.Expected, activity.Text);
})
ยืนยันว่าไม่มีการตอบกลับเพิ่มเติม
หลังจากการยืนยันการตอบกลับที่คาดไว้ทั้งหมดแล้วให้ป้องกันส่วนเกินที่ไม่คาดคิด:
.AssertReply("done")
.AssertNoMoreReplies()
AssertNoMoreReplies() รอ 1 วินาทีและล้มเหลวหากกิจกรรมใด ๆ มาถึง ใช้พารามิเตอร์คําอธิบายเพื่อตั้งชื่อการยืนยันในผลลัพธ์ที่ล้มเหลว:
.AssertNoMoreReplies("only one reply expected per turn")
รวมการส่งและการตรวจสอบด้วย Test()
สำหรับคู่แบบเทิร์นเดียว .Test()จะรวม .Send() และ .AssertReply():
await host.CreateTestFlow()
.Test("ping", "pong")
.Test("foo", "You said: foo")
.StartTestAsync();
การสนทนาแบบหลายรอบ
เชื่อมคู่คำสั่ง send/assert หลายคู่เข้าด้วยกันเพื่อทดสอบลำดับการสนทนาแบบครบถ้วน:
await host.CreateTestFlow()
.Send("start")
.AssertReply("What is your name?")
.Send("Alice")
.AssertReply("Hello, Alice!")
.Send("what can you do?")
.AssertReply(activity => Assert.NotEmpty(activity.Text))
.StartTestAsync();
ทดสอบการตอบกลับหลายรายการต่อหนึ่งรอบ
ตัวแทนสามารถส่งหลายกิจกรรมในการตอบสนองต่อข้อความของผู้ใช้หนึ่ง การเรียกใช้แต่ละครั้ง .AssertReply() จะส่งผลกับกิจกรรมหนึ่งรายการจากคิว:
await host.CreateTestFlow()
.Send("tell me three things")
.AssertReply("First thing.")
.AssertReply("Second thing.")
.AssertReply("Third thing.")
.AssertNoMoreReplies()
.StartTestAsync();
ทดสอบการอัปเดตการสนทนา
ช่องใช้กิจกรรม ConversationUpdate เพื่อแจ้งให้เอเจนต์ทราบเมื่อสมาชิกเข้าร่วมหรือออกจากช่อง ใช้ SendConversationUpdate() เพื่อจําลองเหตุการณ์การรวมที่ทริกเกอร์ข้อความต้อนรับ
ทดสอบกับผู้ใช้เริ่มต้น
ถ้าคุณไม่ส่งผ่านอาร์กิวเมนต์ใดก็ตาม อะแด็ปเตอร์จะเพิ่มผู้ใช้เริ่มต้น (user1) เป็น MembersAdded:
await host.CreateTestFlow()
.SendConversationUpdate()
.AssertReply("Hello and Welcome!")
.StartTestAsync();
ทดสอบกับสมาชิกที่เฉพาะเจาะจง
await host.CreateTestFlow()
.SendConversationUpdate(new[]
{
new ChannelAccount { Id = "alice", Name = "Alice" },
new ChannelAccount { Id = "bob", Name = "Bob" }
})
.AssertReply("Hello and Welcome!")
.StartTestAsync();
ขั้นตอนการต้อนรับและลำดับข้อความแบบเต็ม
await host.CreateTestFlow()
.SendConversationUpdate()
.AssertReply("Hello and Welcome!")
.Send("hello")
.AssertReply("You said: hello")
.AssertNoMoreReplies()
.StartTestAsync();
ตรวจสอบกิจกรรมเต็มรูปแบบ
เมื่อคุณต้องการตรวจสอบคุณสมบัตินอกเหนือจาก Textเช่น Speak, , InputHintSuggestedActionsหรือสิ่งที่แนบมา ให้ใช้การยืนยันผู้รับมอบสิทธิ์:
.AssertReply(activity =>
{
Assert.Equal("Sure, one moment…", activity.Text);
Assert.Equal("Sure, one moment…", activity.Speak);
Assert.Equal(InputHints.IgnoringInput, activity.InputHint);
})
สําหรับไฟล์แนบของการ์ดที่ปรับเปลี่ยนได้ TestAdapter ดำเนินการจัดเรียงแบบอนุกรมแบบไปกลับ ดังนั้น Attachment.Content จะถูกส่งกลับมาเป็น JsonElement ใช้ .ToString() เพื่อแยกค่าสตริง:
.AssertReply(activity =>
{
var card = activity.Attachments
.First(a => a.ContentType == ContentTypes.AdaptiveCard);
// Correct: Content is JsonElement after TestAdapter serialization
string json = card.Content.ToString()!;
Assert.Contains("Seattle", json);
})
ตัวบ่งชี้การพิมพ์
หากเจ้าหน้าที่ของคุณส่งกิจกรรมการพิมพ์ก่อนการตอบกลับจริง ให้ยืนยันอย่างชัดเจน:
.Send("hello")
.AssertTypingIndicator()
.AssertReply("Here is your answer…")
กําหนดชนิดกิจกรรม
.AssertReply(activity =>
{
Assert.Equal(ActivityTypes.Message, activity.Type);
})
การทดสอบที่ขับเคลื่อนด้วยข้อมูล
แทนที่จะเขียนการทดสอบแยกต่างหากสําหรับแต่ละตัวแปรการป้อนข้อมูล ให้ใช้การทดสอบ xUnit Theory ด้วย [InlineData] เพื่อกําหนดพารามิเตอร์:
[Theory]
[InlineData("hi", "You said: hi")]
[InlineData("hello", "You said: hello")]
[InlineData("bye", "You said: bye")]
public async Task Echo_VariousInputs(string input, string expected)
{
await using var host = AgentTestHost.Create(builder =>
{
builder.Services.AddSingleton<IStorage, MemoryStorage>();
builder.Services.AddTransient<IAgent>(sp =>
new EchoAgent(new AgentApplicationOptions(sp.GetRequiredService<IStorage>())));
});
await host.CreateTestFlow()
.Test(input, expected)
.StartTestAsync();
}
สําหรับกรณีการทดสอบที่ซับซ้อนรวมถึงหลายรอบและสถานะการสนทนาที่แตกต่างกันให้ใช้ [MemberData]:
public static IEnumerable<object[]> ConversationFlows()
{
yield return new object[]
{
new string[] { "start", "Alice", "done" },
new string[] { "What is your name?", "Hello, Alice!", "Goodbye, Alice!" }
};
yield return new object[]
{
new string[] { "start", "Bob", "done" },
new string[] { "What is your name?", "Hello, Bob!", "Goodbye, Bob!" }
};
}
[Theory]
[MemberData(nameof(ConversationFlows))]
public async Task MultiTurn_Flow(string[] inputs, string[] expected)
{
await using var host = AgentTestHost.Create(builder =>
{
builder.Services.AddSingleton<IStorage, MemoryStorage>();
builder.Services.AddTransient<IAgent, GreetingAgent>();
});
var flow = host.CreateTestFlow();
for (int i = 0; i < inputs.Length; i++)
{
flow = flow.Test(inputs[i], expected[i]);
}
await flow.StartTestAsync();
}
ใช้ตัวอย่างจําลอง
การทดสอบหน่วยควรทำให้คอมโพเนนต์ที่กำลังทดสอบทำงานอย่างเป็นอิสระ ใช้ Moq เพื่อแทนที่การขึ้นต่อกันภายนอก เช่น บริการ AI ฐานข้อมูล และตัวรู้จํา เพื่อให้การทดสอบยังคงเป็นแบบกําหนดและรวดเร็ว
ส่งการจำลองการขึ้นต่อกันเข้าสู่ระบบ
ออกแบบเอเจนต์ของคุณให้รับการอ้างอิงที่จำเป็นผ่านคอนสตรักเตอร์ แทนที่จะสร้างเองภายใน:
// Harder to test — agent owns the recognizer
public class WeatherAgent : AgentApplication
{
private readonly WeatherService _weather = new WeatherService();
}
// Easier to test — dependency injected
public class WeatherAgent : AgentApplication
{
private readonly IWeatherService _weather;
public WeatherAgent(AgentApplicationOptions options, IWeatherService weather)
: base(options)
{
_weather = weather;
}
}
แทนที่บริการในการทดสอบ
[Fact]
public async Task Weather_ReturnsFormattedForecast()
{
var mockWeather = new Mock<IWeatherService>();
mockWeather
.Setup(s => s.GetForecastAsync("Seattle", It.IsAny<CancellationToken>()))
.ReturnsAsync(new Forecast { TemperatureC = 18, Summary = "Partly cloudy" });
await using var host = AgentTestHost.Create(builder =>
{
builder.Services.AddSingleton<IStorage, MemoryStorage>();
builder.Services.AddSingleton<IWeatherService>(mockWeather.Object);
builder.Services.AddTransient<IAgent>(sp =>
new WeatherAgent(
new AgentApplicationOptions(sp.GetRequiredService<IStorage>()),
sp.GetRequiredService<IWeatherService>()));
});
await host.CreateTestFlow()
.Send("weather in Seattle")
.AssertReplyContains("18")
.StartTestAsync();
}
ตรวจสอบการเรียกใช้บริการ
mockWeather.Verify(
s => s.GetForecastAsync("Seattle", It.IsAny<CancellationToken>()),
Times.Once);
การตรวจสอบความถูกต้องเชิงความหมายโดยใช้ AI
เมื่อเอเจนต์ของคุณเรียกใช้โมเดลภาษา และผลลัพธ์ของมันไม่เป็นแบบกำหนดแน่นอน การยืนยันโดยเทียบข้อความแบบตรงตัวจะเปราะบาง
SemanticValidator ใช้ผู้พิพากษา AI เพื่อประเมินว่าการตอบกลับนั้นสอดคล้องกับคําถามใช่/ไม่ใช่หรือไม่
SemanticValidator
var validator = new SemanticValidator(chatClient, "Does this response describe the weather?");
await host.CreateTestFlow()
.Send("What is the weather in Seattle?")
.AssertReplySatisfies(validator, timeout: 60_000)
.StartTestAsync();
ผู้ตรวจสอบความถูกต้องส่งข้อความตอบกลับของเจ้าหน้าที่ไปยังผู้พิพากษา AI พร้อมคําถามยืนยัน ถ้าผู้พิพากษาตอบว่า "ใช่" การยืนยันก็ผ่านไป หากระบบตอบกลับว่า "ไม่" การทดสอบจะถือว่าล้มเหลว พร้อมข้อความโดยละเอียดซึ่งแสดงพรอมต์และคำตอบที่ได้รับจริง
สร้าง IChatClient
using Azure.AI.OpenAI;
using Azure.Core;
using Microsoft.Extensions.AI;
IChatClient chatClient = new AzureOpenAIClient(
new Uri(endpoint),
new ApiKeyCredential(apiKey)) // ApiKeyCredential is from System.ClientModel
.GetChatClient(deploymentName)
.AsIChatClient();
IResponseValidator แบบกําหนดเอง
นําไปใช้ IResponseValidator เมื่อ SemanticValidator ไม่พอดี ตัวอย่างเช่น เมื่อการตอบกลับเป็นอะแดปทีฟการ์ดแทนที่จะเป็นข้อความธรรมดา:
public class WeatherCardValidator : IResponseValidator
{
private readonly IChatClient _judge;
private readonly string _location;
public WeatherCardValidator(IChatClient judge, string location)
{
_judge = judge;
_location = location;
}
public async Task ValidateAsync(IActivity reply, CancellationToken ct = default)
{
string content;
if (!string.IsNullOrEmpty(reply?.Text))
{
content = reply.Text;
}
else
{
var card = reply?.Attachments?
.FirstOrDefault(a => a.ContentType == ContentTypes.AdaptiveCard);
if (card?.Content == null)
throw new InvalidOperationException("Reply has neither text nor Adaptive Card.");
// Content is JsonElement after TestAdapter round-trip — use ToString()
content = card.Content.ToString()!;
}
var messages = new List<ChatMessage>
{
new(ChatRole.System, "Answer only 'yes' or 'no'."),
new(ChatRole.User, $"Does this contain a weather forecast for {_location}?\n\n{content}")
};
var response = await _judge.GetResponseAsync(messages, cancellationToken: ct);
var answer = response?.Text?.Trim().ToLowerInvariant() ?? string.Empty;
if (answer.StartsWith("yes"))
return;
if (answer.StartsWith("no"))
throw new InvalidOperationException(
$"Validation failed for '{_location}'. Agent replied:\n{content}");
throw new InvalidOperationException($"Unexpected judge response: '{answer}'");
}
}
ใช้:
[Fact(Skip = "Requires Azure OpenAI. Set AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_KEY, AZURE_OPENAI_DEPLOYMENT_NAME.")]
public async Task WeatherAgent_ReturnsSeattleForecast()
{
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!;
string apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY")!;
string deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")!;
IChatClient judge = new AzureOpenAIClient(new Uri(endpoint), new ApiKeyCredential(apiKey))
.GetChatClient(deployment)
.AsIChatClient();
var validator = new WeatherCardValidator(judge, "Seattle");
await using var host = AgentTestHost.Create(builder =>
{
builder.Services.AddSingleton<IStorage, MemoryStorage>();
// ... register Kernel and agent
});
await host.CreateTestFlow()
.SendConversationUpdate()
.AssertReplyContains("Welcome")
.Send("What is the weather forecast for Seattle today?")
.AssertReplySatisfies(validator, timeout: 60_000)
.AssertNoMoreReplies()
.StartTestAsync();
}
Note
การทดสอบที่เรียกใช้บริการ AI ภายนอกทำงานช้าและขึ้นอยู่กับสภาพแวดล้อม ทำเครื่องหมายด้วย [Fact(Skip = "...")] และข้อความที่ชัดเจนเกี่ยวกับตัวแปรสภาพแวดล้อมที่จำเป็น เรียกใช้สิ่งเหล่านั้นในขั้นตอน CI ที่แยกไว้เฉพาะ แทนที่จะรันร่วมกับชุดการทดสอบยูนิตของคุณ
ทดสอบมิดเดิลแวร์
แนบมิดเดิลแวร์กับ host.Adapter ก่อนที่จะสร้างโฟลว์ทดสอบ โฟลว์ทั้งหมดถูกสร้างขึ้นจากกิจกรรมของโพรเซสโฮสต์นั้นผ่านมิดเดิลแวร์
การบันทึกทรานสคริปต์
[Fact]
public async Task Transcript_CapturesAllActivities()
{
await using var host = AgentTestHost.Create(builder =>
{
builder.Services.AddSingleton<IStorage, MemoryStorage>();
builder.Services.AddTransient<IAgent, EchoAgent>();
});
var transcript = new MemoryTranscriptStore();
host.Adapter.Use(new TranscriptLoggerMiddleware(transcript));
await host.CreateTestFlow()
.SendConversationUpdate()
.AssertReply("Hello and Welcome!")
.Send("hello")
.AssertReply("You said: hello")
.AssertNoMoreReplies()
.StartTestAsync();
var activities = await GetTranscriptAsync(
host.Adapter.Conversation.ChannelId,
host.Adapter.Conversation.Conversation.Id,
transcript);
// ConversationUpdate in, welcome out, message in, echo out = 4
Assert.Equal(4, activities.Count);
}
private static async Task<IList<IActivity>> GetTranscriptAsync(
string channelId, string conversationId, ITranscriptStore store)
{
var result = new List<IActivity>();
string token = null;
do
{
var page = await store.GetTranscriptActivitiesAsync(channelId, conversationId, token);
token = page.ContinuationToken;
result.AddRange(page.Items);
} while (token != null);
return result;
}
ใช้ TestAdapter โดยตรง
ใช้ AgentTestHost สำหรับเส้นทางการตั้งค่าที่แนะนำ อย่างไรก็ตาม คุณสามารถสร้าง TestAdapter และ TestFlow ทําได้โดยตรงเมื่อคุณต้องการตัวควบคุมระดับต่ํากว่า หรือกําลังทดสอบการเรียกกลับแบบสแตนด์อโลน
[Fact]
public async Task DirectAdapter_Echo()
{
var adapter = TestAdapter.Create(channelId: Channels.Msteams);
async Task BotCallback(ITurnContext tc, CancellationToken ct)
{
if (tc.Activity.Type == ActivityTypes.Message)
await tc.SendActivityAsync($"echo:{tc.Activity.Text}", cancellationToken: ct);
}
await new TestFlow(adapter, BotCallback)
.Test("foo", "echo:foo")
.StartTestAsync();
}
กําหนดการอ้างอิงการสนทนา
var adapter = TestAdapter.Create(
channelId: "msteams",
userId: "u42",
userName: "Alice",
botId: "mybot",
botName: "MyBot",
conversationId: "conv-42",
conversationName: "Test Room");
ตั้งค่าการอ้างอิงการสนทนาโดยตรงบนอะแด็ปเตอร์ของโฮสต์ก่อนที่จะสร้างโฟลว์:
host.Adapter.Conversation = new ConversationReference
{
ChannelId = Channels.Msteams,
User = new ChannelAccount("alice", "Alice"),
Agent = new ChannelAccount("bot", "Bot"),
Conversation = new ConversationAccount(false, "conv-1", "Conversation 1"),
ServiceUrl = "https://test.example.com"
};
การทดสอบ OAuth และโฟลว์โทเค็น
TestAdapter แสดงวิธีการใส่ข้อมูลโทเค็นผู้ใช้ไว้ล่วงหน้า วิธีการเหล่านี้ช่วยให้คุณทดสอบตัวแทนที่ใช้ GetUserTokenAsync หรือโฟลว์การลงชื่อเข้าระบบครั้งเดียวโดยไม่ต้องเชื่อมต่อกับผู้ให้บริการข้อมูลประจําตัวจริง
ใส่โทเค็นล่วงหน้า
host.Adapter.AddUserToken(
connectionName: "MyOAuthConnection",
channelId: Channels.Test,
userId: "user1",
token: "fake-access-token");
เตรียมโทเค็นที่สามารถแลกเปลี่ยนได้สําหรับการลงชื่อเข้าระบบครั้งเดียว
host.Adapter.AddExchangeableToken(
connectionName: "MyOAuthConnection",
channelId: Channels.Test,
userId: "user1",
exchangableItem: "sso-token",
token: "fake-access-token");
จําลองความล้มเหลวในการแลกเปลี่ยน
host.Adapter.ThrowOnExchangeRequest(
connectionName: "MyOAuthConnection",
channelId: Channels.Test,
userId: "user1",
exchangableItem: "bad-sso-token");
แทรกการหน่วงเวลา
ใช้ .Delay() เมื่อเจ้าหน้าที่ของคุณมีพฤติกรรมที่พึ่งพาเวลา
await host.CreateTestFlow()
.Send("start")
.Delay(TimeSpan.FromSeconds(2))
.Send("are you still there?")
.AssertReply("Yes, I'm here.")
.StartTestAsync();
ค่าเริ่มต้นของการหมดเวลาและการแก้ไขจุดบกพร่อง
วิธีการยืนยันทั้งหมดมีพารามิเตอร์เป็นtimeoutมิลลิวินาที โดยมีค่าเริ่มต้นเป็น3000 เมื่อคุณแนบตัวแก้ไขจุดบกพร่อง Visual Studio ระบบจะตั้งค่าการหมดเวลาโดยอัตโนมัติเป็น uint.MaxValue เพื่อให้จุดสั่งหยุดไม่ทําให้เกิดความล้มเหลวรุนแรง
สำหรับการดำเนินการของเอเจนต์ที่ใช้เวลานาน เช่น การเรียกใช้โมเดลภาษา ควรกำหนดค่า timeout อย่างชัดเจน
.AssertReplySatisfies(validator, timeout: 60_000)
AssertNoMoreReplies() ใช้ค่าเริ่มต้นที่สั้นลงของ 1000 ms เนื่องจากตัวแทนการประมวลผลเสร็จสิ้นแล้ว
การอ้างอิง
AgentTestHost
| สมาชิก | Description |
|---|---|
AgentTestHost.Create(configure) |
สร้างและเริ่มต้นโฮสต์ ลงทะเบียน TestAdapter ล่วงหน้าเป็น IChannelAdapter |
host.Adapter |
อินสแตนซ์TestAdapter แนบมิดเดิลแวร์ที่นี่ |
host.CreateTestFlow() |
ดึง IAgent จาก DI และส่งกลับ TestFlow |
host.DisposeAsync() |
หยุดโฮสต์ ใช้ await using เพื่อกําจัด |
TestAdapter
| สมาชิก | Description |
|---|---|
TestAdapter.Create(...) |
ตัวสร้างสำหรับอะแดปเตอร์ที่มีการกำหนดค่าไว้อย่างสมบูรณ์ |
adapter.Conversation |
ConversationReference ใช้สำหรับทุกกิจกรรม |
adapter.ActiveQueue |
คิวกิจกรรมที่เอเจนต์ส่ง |
adapter.Use(middleware) |
แนบมิดเดิลแวร์ ส่งคืน this สำหรับการเกี่ยวโยง |
adapter.AddUserToken(...) |
เตรียมโทเค็นผู้ใช้สําหรับการทดสอบ OAuth |
adapter.AddExchangeableToken(...) |
กรอกโทเค็นที่แลกเปลี่ยนได้สำหรับ SSO ล่วงหน้า |
adapter.ThrowOnExchangeRequest(...) |
กําหนดค่าคําขอ Exchange ที่จะส่ง |
วิธี TestFlow
| วิธีการ | Description |
|---|---|
.Send(text) |
ส่งกิจกรรมประเภทข้อความ |
.Send(activity) |
ส่งประเภทกิจกรรมใดก็ได้ |
.SendConversationUpdate() |
ส่ง ConversationUpdate ให้กับผู้ใช้เริ่มต้น |
.SendConversationUpdate(members) |
ส่ง ConversationUpdate ให้กับสมาชิกที่เฉพาะเจาะจง |
.Test(input, expected) |
.Send()
+
.AssertReply() แบบย่อ |
.AssertReply(string) |
การตรงกันของข้อความแบบตรงกันทุกประการ (ตัดช่องว่าง) |
.AssertReplyContains(string) |
การจับคู่สตริงย่อย |
.AssertReplyOneOf(string[]) |
จับคู่สตริงใดสตริงหนึ่งจากหลายสตริง |
.AssertReply(Activity) |
จับคู่ตามชนิดกิจกรรมและข้อความ |
.AssertReply(Action<IActivity>) |
การตรวจสอบการซิงค์แบบกําหนดเอง ส่งเป็นล้มเหลว |
.AssertReplySatisfies(Func<IActivity, Task>) |
การตรวจสอบความถูกต้องแบบอะซิงก์ที่กำหนดเอง |
.AssertReplySatisfies(IResponseValidator) |
มอบหมายไปยังวัตถุตัวตรวจสอบ |
.AssertTypingIndicator() |
ต้องมีกิจกรรมการพิมพ์ |
.AssertNoMoreReplies() |
ล้มเหลวหากกิจกรรมใด ๆ มาถึงภายใน 1 วินาที |
.Delay(ms) |
แทรกการหยุดชั่วคราวในโฟลว์ |
.StartTestAsync() |
ประมวลผลคำสั่งในห่วงโซ่ |
SemanticValidator
| สมาชิก | Description |
|---|---|
new SemanticValidator(chatClient, prompt) |
สร้างตัวตรวจสอบ
prompt คือคําถามใช่/ไม่ใช่เกี่ยวกับการตอบกลับ |
ValidateAsync(activity, ct) |
ถือว่าผ่านหาก AI ตอบว่า "ใช่"; จะเกิดข้อยกเว้นหากตอบว่า "ไม่" หรือคำตอบอื่นที่ไม่คาดคิด |