10.智能金融平台的测试与调试
10.智能金融平台的测试与调试
一、项目概览
这个项目不是一个普通的单体应用。整个平台的架构横跨了三个前端和一个后端:
- Flutter 移动端(Digital_Intelligence_Finance_Frontend):同时承载普通页面和 Unity 内嵌场景
- Unity 金融游戏(内嵌于 Flutter):通过 postMessage 双向通信,管理题库缓存、结算逻辑
- FastAPI 后端(Digital_Intelligence_Finance_backend):包含 7 个 Agent、RAG 引擎、ChromaDB 向量库、Backtrader 回测引擎
从测试的角度来看,每一层都有独立的测试策略和工具链。这篇博客不会泛泛讲理论,而是把我们项目中真实使用的测试手段、踩过的坑、积累的套路写下来。
二、后端测试:pytest + httpx 做集成验证
2.1 测试框架选型
后端基于 FastAPI,很自然选择了 pytest + httpx 组合。配置文件很简单:
# pytest.ini
[pytest]
testpaths = tests
测试文件全部放在 tests/ 目录下,目前有:
test_api.py— API 集成测试(注册/登录/健康检查)test_rag_service.py— RAG 知识问答服务的单元测试
2.2 API 集成测试:用 ASGI Transport 避免起服务
一个实用的技巧是使用 ASGITransport 直接向 FastAPI app 实例发送请求,完全不需要先 uvicorn 启动服务。
@pytest.mark.anyio
async def test_health_check():
from app.main import app
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as client:
resp = await client.get("/health")
assert resp.status_code == 200
assert resp.json()["status"] == "healthy"
同样模式覆盖注册和登录流程,验证 JWT token 能否正常生成。这种方法的好处是保持测试独立运行,不需要依赖真实数据库——也可以替换为内存 SQLite 来隔离环境。
2.3 RAG 服务的测试:Mock 一切外部依赖
RAG 知识问答服务依赖 ChromaDB 和 LLM,是项目中最复杂的业务模块之一。测试策略是彻底 mock 掉外部调用。
核心技巧是用 pytest 的 monkeypatch 替换 chroma_client.query 和 LLM 响应。然后用一个本地的 FakeLLM 类模拟大模型响应,验证 prompt 中是否包含正确的用户水平前缀、返回的结构是否完整、confidence 是否被 clamp 到合法范围、answer_style 是否被规范化。
这个测试模式后来成为我们所有 LLM 相关功能的测试样板,核心思路是,不测 LLM 本身,只测自己的逻辑是否正确处理了 LLM 的输入输出。
三、Flutter 端测试:Widget Test 与状态管理验证
3.1 冒烟测试
Flutter 端的测试目前集中在 widget_test.dart 的冒烟测试阶段,验证 MaterialApp 的构建不崩溃、路由注册正确、Provider 初始化不报错。
3.2 Flutter + Unity 的调试特殊性
Flutter 内嵌 Unity 的场景给调试带来了额外的挑战。实际调试时使用 adb logcat 进行完整的链路追溯。
典型的数据流(从拉题到结算):
Flutter 拉题 (HTTP batch)
→ postMessage 下发配置 / 逐题 ApplyQuestion (GameManager)
→ Unity 缓存题目(可选直连 HTTP 兜底)
→ Unity 回传 questions_cached
→ Flutter 解除"同步中"状态
调试时的关键日志特征:
| 环节 | 正常日志 |
|---|---|
| Flutter prefetch | [FinanceGamePage] prefetch quiz ok |
| Unity 收到配置 | [FinanceGameManager] 运行配置已更新 |
| Unity 缓存完成 | [QuestionManager] 已缓存题目数量: 3 |
| 回传确认 | [FinanceGamePage] Unity confirmed 3 cached questions |
四、真机调试实录
4.1 问题现象
真机上打开金融知识闯关,Unity 内题目区一直显示"题目准备中",约 45 秒后 Flutter 顶部出现红色提示条:“Unity 未确认题目缓存”。
4.2 分层验证方法
第一层 环境验证:通过 run-as 读取 SharedPreferences 确认 api_host 已写入局域网 IP,access_token 存在,手机浏览器访问 http://<IP>:8000/docs 返回 200。结论:手机到后端的通路是通的。
第二层 logcat 日志分析:用 flutter run --dart-define=API_HOST=... --route=/finance-game 运行时,日志呈现出清晰的分工:
| 环节 | 结果 |
|---|---|
| Flutter prefetch quiz | 正常,batch 200,拿到 3 道题 |
| SetRuntimeConfig | 正常,game_ready 后能收到 |
| Unity UnityWebRequest 直连 | 失败——Insecure connection not allowed |
| FlutterAndroidConfig 读 prefs | 失败——OnBackInvokedError 类缺失 |
结论:不是"没题目",而是"题目没进 Unity 的缓存"。
4.3 三个问题叠加
这个问题由三个独立的问题叠加导致:
问题 1:Unity 默认禁止 HTTP 明文。Flutter 可以在 Manifest 设置 usesCleartextTraffic,但 Unity 还有自己的 Player Settings。insecureHttpOption: 0 时,真机上 UnityWebRequest 访问 http://192.168.x.x:8000 直接被拒绝。修复:导出前将 Allow downloads over HTTP 设为允许(改为 insecureHttpOption: 2),并重新 Export Android。
问题 2:Android Java 互操作的坑。新增的 FlutterAndroidConfig 用 currentActivity 获取 Context,在真机上触发 NoClassDefFoundError: OnBackInvokedCallback。教训:在 Unity 里调 Android API,优先 getApplicationContext(),少碰 Activity 生命周期相关类。
问题 3:跨端消息的目标对象与时序。早期日志出现 SendMessage: object FinanceGameManager not found!。实际场景里 GameObject 叫 GameManager,并且场景加载时机和 postMessage 抵达时机有竞态,需要设置接收后才开始发送。
4.4 可复用的排查清单
如果你也在做 Flutter 嵌入 Unity 的 Android 真机联调,可以按这个顺序排查:
- SharedPreferences 中
api_host和token是否与电脑 IP 一致 - 手机能否访问
http://<IP>:8000/docs - Flutter 日志:batch 是否 200、
prefetch quiz ok是否出现 - Unity 日志:是否有
Insecure connection、SendMessage ... not found - 是否收到
questions_cached回传消息 - 改 Player Settings / C# 后是否重新 Export Android
- Export 前 Console 中是否有
error CS编译错误 game_completed里本局金币与总金币字段是否混淆- 再次挑战后场景是否重载 + 题目重新下发
- UI 是否依赖硬编码像素;Canvas 是否配 Scaler + Safe Area
五、日志系统:结构化的可观测性
5.1 日志架构
后端的日志配置在 app/core/logger.py,采用三级输出:
- 控制台处理器:INFO 及以上,适合开发时实时查看
- 文件处理器:DEBUG 及以上,按 10MB 轮转,保留 5 个备份
- 错误处理器:ERROR 及以上,单独存储,保留 3 个备份
格式包含时间戳、日志级别、模块名和行号:
2026-04-11 21:43:57 | INFO | finance_platform:172 - [NewsAgent] 从 新浪财经 抓取 20 条
2026-04-11 21:43:57 | WARNING | finance_platform:174 - [NewsAgent] 抓取失败 东方财富: Expecting value: line 1 column 1 (char 0)
5.2 自定义异常映射
定义了两个业务层可识别的异常,避免未捕获异常导致 500:
class LLMUpstreamError(Exception):
"""大模型上游错误(密钥、鉴权、额度等),映射为 502"""
class MarketDataUnavailableError(Exception):
"""行情类上游限流或不可用,映射为 503"""
在 FastAPI 的 exception_handler 中统一捕获,返回结构化 JSON:
{"code": 502, "message": "上游 API 鉴权失败", "data": null}
六、总结
这个项目的测试和调试体系覆盖了从最底层的 Python 单元测试到最高层的真机 Flutter-Unity 联调,几个贯穿始终的原则:
分层验证——无论是后端 API 还是跨端通信,"卡住"是一个表象。把可能的环节拆成可独立验证的小层,每层都有明确的通过标准,就能把猜测变成证据。
Mock 外部依赖——LLM、ChromaDB、行情数据这些不可控的第三方,在单元测试中都应当 mock 掉。测试只能测自己的代码逻辑是否正确,不测别人是否在线。
日志即调试——结构化日志 + 唯一的日志特征词是真机调试时最快的定位手段,比断点更可靠,因为真机没有调试器。
从功能测试到质量评估——功能测试回答"做没做对",RAGAS 评估回答"做得好不好",chunk 分析回答"能不能做得更好"。三者结合才构成完整的测试闭环。
本地复现:后端在 Digital_Intelligence_Finance_backend/ 运行 python main.py(8000 端口);Flutter 前端 flutter run --dart-define=API_HOST=<局域网IP> --route=/finance-game;Web 前端 cd src && npm run dev;单元测试 pytest tests/。
更多推荐



所有评论(0)