内容提要
本文介绍Flutter中AI功能的测试策略,强调测试应聚焦于自身代码而非AI模型。提出三层测试架构:单元测试(仓库层、Bloc状态)、组件测试(聊天界面、流式渲染)和集成测试(Firebase模拟器)。核心内容包括模拟AI客户端、测试流式响应、错误状态、系统提示词安全、速率限制等,并提供完整测试工具和最佳实践,帮助开发者构建可靠的AI功能测试套件。
延伸解读
测试的核心:你的代码,而非模型
文章强调,AI功能测试的重点不是验证Gemini模型本身,而是你自己的代码:仓库层、状态管理、界面渲染、错误处理等。模型由Google负责,你的代码才是可控且必须测试的部分。通过模拟AI客户端,可以确定性测试所有逻辑,避免因模型输出非确定性而放弃测试。
三层测试架构:从单元到集成
文章提出三层测试架构:单元测试(仓库、Bloc、速率限制等)、组件测试(聊天界面、流式渲染)和集成测试(Firebase模拟器)。这种金字塔结构强调单元测试数量最多、速度最快,集成测试最少但最接近真实环境。大多数bug由前两层捕获,集成测试补充系统级问题。
模拟AI客户端的正确姿势
模拟AI客户端是测试的基础。文章指出,模拟必须匹配真实客户端的响应结构,如GenerateContentResponse的candidates和finishReason,否则测试会通过但生产代码失败。同时,依赖注入是测试性的前提,通过构造函数注入仓库和速率限制器,使测试能替换为mock。
流式测试:模拟分块响应
流式响应是AI功能测试的难点。文章提供fakeStreamedResponse生成器,逐词模拟流式输出,并加入延迟以模拟真实时序。测试需验证Bloc正确累积分块,以及界面在流式更新时正确替换文本。这能捕获时序相关的bug,如重复发送或中途关闭导致的崩溃。
Q&A
在Flutter中测试AI功能时,应该测试哪些部分?
应该测试自己的代码,而不是AI模型。具体包括:仓库层(映射响应、处理错误、记录token用量)、状态管理(Bloc状态转换、流式处理、速率限制)、组件层(加载指示器、AI归属标签、标记按钮、重试横幅、发送按钮禁用状态)以及横切关注点(提示词韧性、离线行为、重复请求预防、流取消)。
为什么在Flutter中测试AI功能时不能使用真实的AI客户端?
因为真实客户端会发起网络请求,导致测试变慢、不稳定(受网络和配额影响)且产生费用。测试应使用mock来模拟AI客户端,以便快速、可靠地验证自己的代码逻辑。
在Flutter中测试AI功能时,如何模拟流式响应?
可以使用async*生成器函数创建模拟流,例如fakeStreamedResponse函数,它逐词产出GenerateContentResponse对象,并在每次产出之间添加延迟,以模拟真实的流式传输。这样可以在测试中验证UI对流的累积和渲染。
在Flutter中测试AI功能时,如何测试系统提示词的韧性?
通过编写对抗性输入测试,验证系统提示词在恶意输入下是否仍然有效。例如,测试PromptSanitizer是否能正确清理输入,以及系统提示词内容是否完整未被篡改。
在Flutter中测试AI功能时,如何处理错误状态和速率限制?
需要测试各种错误状态,如内容被阻止、配额超限、网络错误等,确保显示人类可读的错误消息。对于速率限制,可以模拟速率限制器,测试当请求超过限制时是否正确显示错误消息。
在Flutter中测试AI功能时,集成测试的作用是什么?
集成测试使用Firebase Local Emulator模拟真实环境,测试完整的应用流程,包括真实数据流、生命周期事件和离线网络行为,而无需调用真实的Gemini API。它用于捕获单元测试和组件测试无法发现的、仅在完整系统中出现的问题。
在Flutter中测试AI功能时,如何测试流式累积逻辑?
在Bloc测试中,可以使用StreamController模拟流式响应,逐步添加累积的文本块,并验证Bloc是否正确累积并发出更新后的状态。例如,先发出'Hello',再发出'Hello world',然后断言状态中的streamingContent为'Hello world'。
在Flutter中测试AI功能时,如何测试发送按钮在流式传输期间被禁用?
在组件测试中,将Bloc的状态设置为ChatStreaming,然后检查发送按钮的onPressed是否为null。例如,使用tester.widget<FilledButton>找到按钮,并断言其onPressed为null。