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 互操作的坑。新增的 FlutterAndroidConfigcurrentActivity 获取 Context,在真机上触发 NoClassDefFoundError: OnBackInvokedCallback。教训:在 Unity 里调 Android API,优先 getApplicationContext(),少碰 Activity 生命周期相关类。

问题 3:跨端消息的目标对象与时序。早期日志出现 SendMessage: object FinanceGameManager not found!。实际场景里 GameObject 叫 GameManager,并且场景加载时机和 postMessage 抵达时机有竞态,需要设置接收后才开始发送。

4.4 可复用的排查清单

如果你也在做 Flutter 嵌入 Unity 的 Android 真机联调,可以按这个顺序排查:

  1. SharedPreferences 中 api_hosttoken 是否与电脑 IP 一致
  2. 手机能否访问 http://<IP>:8000/docs
  3. Flutter 日志:batch 是否 200、prefetch quiz ok 是否出现
  4. Unity 日志:是否有 Insecure connectionSendMessage ... not found
  5. 是否收到 questions_cached 回传消息
  6. 改 Player Settings / C# 后是否重新 Export Android
  7. Export 前 Console 中是否有 error CS 编译错误
  8. game_completed 里本局金币与总金币字段是否混淆
  9. 再次挑战后场景是否重载 + 题目重新下发
  10. 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/

Logo

一站式 AI 云服务平台

更多推荐