如何创建Azure.AI.OpenAI.ChatCompletions模拟对象以测试OpenAI相关函数?
测试调用Azure OpenAI的函数:解决ChatCompletions模拟问题
方法一:通过反射创建ChatCompletions实例
由于ChatCompletions没有公开构造函数,我们可以利用反射实例化它,并构建所需的响应结构:
步骤1:构建基础响应对象
先创建模拟的ChatMessage和ChatChoice(这两个类有公开构造函数):
var mockMessage = new ChatMessage(ChatRole.Assistant, "这是模拟的LLM响应内容"); var mockChoice = new ChatChoice(0, mockMessage, CompletionsFinishReason.Stop);
步骤2:反射创建ChatCompletions实例
// 获取ChatCompletions的私有构造函数 var chatCompletionsType = typeof(ChatCompletions); var constructor = chatCompletionsType.GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes); // 实例化ChatCompletions var chatCompletions = (ChatCompletions)constructor.Invoke(null); // 设置Choices属性(通过反射访问) var choicesProperty = chatCompletionsType.GetProperty("Choices"); choicesProperty.SetValue(chatCompletions, new List<ChatChoice> { mockChoice }.AsReadOnly());
步骤3:创建Response并模拟客户端
使用Azure.Core的Response.FromValue方法构建响应对象,再用Moq模拟OpenAIClient:
// 创建模拟的原始Response(用Moq快速实现) var mockRawResponse = new Mock<Response>().Object; var mockResponse = Response.FromValue(chatCompletions, mockRawResponse); // 模拟OpenAIClient的GetChatCompletionsAsync方法 var mockClient = new Mock<OpenAIClient>(); mockClient.Setup(c => c.GetChatCompletionsAsync( It.IsAny<string>(), It.IsAny<ChatCompletionsOptions>(), It.IsAny<CancellationToken>())) .ReturnsAsync(mockResponse);
方法二:抽象客户端接口(更优的可测试性方案)
为了避免依赖具体实现类带来的测试麻烦,建议给OpenAIClient套一层接口:
步骤1:定义接口
public interface IOpenAIChatClient { Task<Response<ChatCompletions>> GetChatCompletionsAsync( string deploymentName, ChatCompletionsOptions conversation, CancellationToken cancellationToken = default); }
步骤2:实现接口包装类
public class OpenAIChatClient : IOpenAIChatClient { private readonly OpenAIClient _client; public OpenAIChatClient(OpenAIClient client) { _client = client; } public async Task<Response<ChatCompletions>> GetChatCompletionsAsync( string deploymentName, ChatCompletionsOptions conversation, CancellationToken cancellationToken = default) { return await _client.GetChatCompletionsAsync(deploymentName, conversation, cancellationToken) .ConfigureAwait(false); } }
步骤3:重构业务函数并测试
将原函数中的OpenAIClient替换为IOpenAIChatClient,测试时直接模拟接口:
// 模拟接口返回预期响应 var mockChatClient = new Mock<IOpenAIChatClient>(); mockChatClient.Setup(c => c.GetChatCompletionsAsync( It.IsAny<string>(), It.IsAny<ChatCompletionsOptions>(), It.IsAny<CancellationToken>())) .ReturnsAsync(mockResponse); // mockResponse可通过方法一的方式创建 // 注入模拟对象到业务类并执行测试 var service = new YourService(mockChatClient.Object); var result = await service.GetChatCompletion("test-deployment", new ChatCompletionsOptions()); Assert.Equal("这是模拟的LLM响应内容", result);
注意事项
- 使用反射时需注意:Azure SDK的内部结构可能随版本变化,升级SDK后需验证反射代码是否仍有效。
- 接口抽象方案更符合依赖倒置原则,能提升代码的可维护性和可测试性,推荐优先使用。
内容的提问来源于stack exchange,提问作者Mark Wiemer
相关产品推荐
相关产品推荐

