You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何对Flask-Caching缓存的memoize函数进行单元测试?

环境版本
  • Python 3.9.13
  • pytest==7.2.0
  • pytest-mock==3.10.0
  • Flask==2.2.2
  • Flask-Caching==2.0.1
待测试类代码
from dataclasses import dataclass
from flask_caching import Cache

# 假设全局已初始化缓存实例
cache = Cache()

@dataclass(frozen=True)
class NominationData:
    title: str
    url: str
    is_approved: bool
    articles: list[ArticleData]
    hooks: list[HookData]

    @staticmethod
    @cache.memoize(timeout=30)
    def from_nomination(nomination):
        """Construct a NominationData from a dyk_tools.Nomination"""
        return NominationData(
            nomination.title(),
            nomination.url(),
            nomination.is_approved(),
            [ArticleData.from_article(a) for a in nomination.articles()],
            [HookData.from_hook(h) for h in nomination.hooks()],
        )
问题描述

未添加@cache.memoize()装饰器时,NominationData.from_nomination()的单元测试可正常运行;添加装饰器后,测试在flask_caching/__init__.py:870处报错AttributeError: 'Cache' object has no attribute 'app',原因是缺少有效的Flask应用上下文。尝试过mock Flask上下文、拆分缓存逻辑为独立垫片函数两种思路,但均不够理想,求最优解决方法。

最优解决方案

推荐两种方向,可根据测试需求选择:

方向一:在测试中初始化最小Flask上下文(适合验证缓存行为的场景)

直接创建最小Flask应用实例并绑定缓存,为测试提供合法上下文,无需mock核心逻辑:

  1. 编写pytest fixture初始化应用和上下文:
import pytest
from flask import Flask
from your_module import cache, NominationData

@pytest.fixture(scope="module")
def test_app():
    app = Flask(__name__)
    # 使用内存缓存,避免依赖外部存储,适配单元测试场景
    app.config["CACHE_TYPE"] = "SimpleCache"
    # 将全局缓存绑定到测试应用
    cache.init_app(app)
    return app

@pytest.fixture(scope="function")
def app_context(test_app):
    # 为每个测试函数注入应用上下文
    with test_app.app_context():
        yield
  1. 在测试函数中使用app_context fixture:
def test_from_nomination(app_context, mocker):
    # Mock 依赖的nomination对象及方法
    mock_nom = mocker.Mock()
    mock_nom.title.return_value = "测试标题"
    mock_nom.url.return_value = "https://example.com"
    mock_nom.is_approved.return_value = True
    
    # Mock articles和hooks的返回结果
    mock_article = mocker.Mock()
    mock_nom.articles.return_value = [mock_article]
    mock_hook = mocker.Mock()
    mock_nom.hooks.return_value = [mock_hook]
    
    # Mock 嵌套的from_article和from_hook方法
    mocker.patch("your_module.ArticleData.from_article", return_value=mocker.Mock())
    mocker.patch("your_module.HookData.from_hook", return_value=mocker.Mock())
    
    # 执行测试并断言
    result = NominationData.from_nomination(mock_nom)
    assert result.title == "测试标题"
    assert result.url == "https://example.com"
    assert result.is_approved is True

该方法可完整测试带缓存的逻辑,包括缓存命中、超时等行为,适合需要验证缓存有效性的集成测试场景。

方向二:拆分业务逻辑与缓存逻辑(适合专注测试业务逻辑的场景)

将缓存装饰器与核心业务逻辑分离,让单元测试直接针对无缓存的业务逻辑,避免上下文依赖:

  1. 重构原代码:
@dataclass(frozen=True)
class NominationData:
    # 字段保持不变

    @staticmethod
    def _from_nomination(nomination):
        """核心业务逻辑,无缓存依赖"""
        return NominationData(
            nomination.title(),
            nomination.url(),
            nomination.is_approved(),
            [ArticleData.from_article(a) for a in nomination.articles()],
            [HookData.from_hook(h) for h in nomination.hooks()],
        )

    @staticmethod
    @cache.memoize(timeout=30)
    def from_nomination(nomination):
        """对外暴露的带缓存接口"""
        return NominationData._from_nomination(nomination)
  1. 单元测试直接测试核心逻辑:
def test_from_nomination_core(mocker):
    # Mock 依赖对象
    mock_nom = mocker.Mock()
    mock_nom.title.return_value = "测试标题"
    mock_nom.url.return_value = "https://example.com"
    mock_nom.is_approved.return_value = True
    
    mock_article = mocker.Mock()
    mock_nom.articles.return_value = [mock_article]
    mock_hook = mocker.Mock()
    mock_nom.hooks.return_value = [mock_hook]
    
    mocker.patch("your_module.ArticleData.from_article", return_value=mocker.Mock())
    mocker.patch("your_module.HookData.from_hook", return_value=mocker.Mock())
    
    # 直接测试核心业务逻辑
    result = NominationData._from_nomination(mock_nom)
    assert result.title == "测试标题"
    # 其他业务逻辑断言...

这种方法让单元测试聚焦业务逻辑,无需处理Flask上下文;缓存的有效性可单独通过集成测试验证(比如连续调用两次from_nomination,检查_from_nomination是否仅执行一次)。


内容的提问来源于stack exchange,提问作者Roy Smith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 20:35:16